Google Cloud - instance 間通信(pubsub + memcache 實現實例間通信和保證一致性)

GCP - appengine 通過 version 管理應用,你可以在 appengine 上部署多個 version(dev、qa等),而每個 version 可以有多個 instance,一個 instance 可簡單理解爲一個基於 Spring Boot 實現的微服務,當有請求到達時 appengine 會根據一定策略選擇由哪一個 instance 處理該請求,如果現有的 instance 處理的流量已經很多,那麼 appengine 會啓動新的 instance 來處理這個請求,這個行爲主要由 instance 的三種擴縮策略決定:手動、自動和基礎。

背景

應用數據保存在 GCP 的數據存儲組件 datastore 中,datastore 是一個 NoSQL 數據庫。假設我們的應用對其中一部分數據 M 的操作的 QPS 要求很高,如果每次都從 datastore 查詢則不能滿足需求。

// 更新 M 數據
POST /m

// 查詢 M 數據
GET /m

解決方案

爲了加快請求的處理速度,我們在應用啓動時(即 instance 啓動時)先將這部分數據全部加載到內存,之後直接從內存中讀取,而不是每次都從 datastore 中查詢。這種方式有幾個問題需要解決:

  1. 每次請求到達時不確定 appengine 會將請求路由給哪一個 instance,所以當這部分數據有更新(POST /m)時需要通知該 version 的所有 instance 進行數據同步
  2. POST /m 請求中要確保所有 instance 都成功同步了數據(所有 instance 中 M 數據保持一致),才能以請求處理成功的狀態返回。

數據同步 - pubsub

instance 間通信是一個棘手的問題,因爲 appengine 沒有直接提供 API 或外部組件來完成這件事情,甚至你想將請求發給指定的 instance 都需要犧牲很多靈活性才能實現。當然大多數情況下請求的路由應該由 appengine 控制,只有在實現某些特殊需求時纔可以考慮一些特殊做法。

數據同步的基本思路是在 POST /m 時先在 datastore transactions 中更新 M 數據,事務成功提交後再通過 pubsub 通知所有 instance 從 datastore 更新最新數據,所有 instance 都確認成功更新數據後,請求成功。

通過查閱 appengine 請求路由說明可以知道通過以下格式的地址可以將請求路由給指定的 instance。
https://[INSTANCE_ID]-dot-[VERSION_ID]-dot-[SERVICE_ID]-dot-[MY_PROJECT_ID].appspot.com
但這種方式需要將擴縮方式設置爲手動擴縮,而且 INSTANCE_ID並不是 instance 的唯一 id,而是一個下標索引,比如 version test 有 3 個 instance,分別爲 A、B、C,那麼可以保證的是通過 test[0]、test[1]、test[2] 可以成功訪問(遍歷)三個 instance,但你無法知道 test[0] 究竟是指向 A、B 還是 C。

pubsub 是 GCP 的消息組件,消息發送後消費者有兩種方式消費消息:pullpush

  1. pull: 通過拉取的方式消費消息,我們的目的是將“數據同步”這個消息立刻通知到每一個 instance,pull 的方式需要每一個 instance 以輪詢的方式檢查並拉取消息,這樣在資源佔用和響應速度(POST /m)上都不能滿足要求。
  2. push: 這種方式在消息發到 pubsub 的指定 topic 後,pubsub 會立刻把這個消息 push 到訂閱了這個 topic 的所有 subscriber (subscriber 指定的 endpoint 處,這裏我們的域需要設置爲:https://[INSTANCE_ID]-dot-[VERSION_ID]-dot-[SERVICE_ID]-dot-[MY_PROJECT_ID].appspot.com)。

需要注意的是 pubsub 的 tpoic 和 subscriber 的創建只需要執行一次,可以在 version 啓動後通過 appengine-taskqueue 創建 n 個 subscriber,n 爲這個 version 的實例數。這意味着實例數量 n 是個"常數"(只有手動擴縮模式才能確保 n 爲“常數”)。

到這裏,在 POST /m 裏通知所有 instance 進行 “數據同步”這個消息可以正確發送並最終通知到所有 instance 了。使用 appengine 預留的 /_ah/push-handlers/.* 路徑可以簡化 endpoint 的認證和授權,最終的 endpoint 是下面的形式:
https://[INSTANCE_ID]-dot-[VERSION_ID]-dot-[SERVICE_ID]-dot-[MY_PROJECT_ID].appspot.com/_ah/push-handlers/your-topic-name
pubsub 進行 push 時每個 instance 的 POST /_ah/push-handlers/your-topic-name controller/handler 就會收到請求,並攜帶着消息內容。在這個請求中,我們可以從消息中知道需要怎樣更新數據,進而完成數據同步的任務。

一致性 - memcahce

上面介紹了數據同步的具體流程,在這個過程中一致性的保證是很重要的,主要體現在數據同步時需要確保所有的 instance 都成功消費“數據同步”消息,POST /m 才能以請求成功處理的狀態返回。

在這裏 version 的 instance 數量 n 是“常量”,那麼我們只需在一個公共的地方維護一個標識 A,標識當前已經成功同步的 instance 數量,當這個 A == n 時也就意味着所有 instance 都成功同步了數據。

appengine-memcache 是主要應用於 appengine 上的分佈式緩存服務,我們可以在上面存儲這個唯一標識,POST /m 請求中成功發送“數據同步”消息後,就以輪詢的方式從 memcache 中查詢 A 的值,同時在 POST /_ah/push-handlers/your-topic-name 中要遞增 A 的值,A == n 時就表明同步完成。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章