可以想象的是,我們每天推送的通知數量有多大——大概每小時 1 百萬個。這篇文章將介紹我們在處理如此體量的推送通知時所面臨的挑戰,以及我們的解決方案。
體量大還只是其中的一個方面,在 Gojek,我們還需要面對一些獨有的問題。
01 多個應用程式Gojek 不只有一個 App,除了使用者 App,我們還有 GoLife、司機 App、商家 App,還有服務商 App。
當我們的系統要推送通知,要麼是推給某個使用者的 App(例如,推給 GoLife 的通知不會被推到 Gojek 上),要麼是推給所有的 App(例如促銷通知)。
我們的系統需要足夠靈活,能夠在廣播和單獨推送之間自由地選擇。
02 多個通知服務提供商因為我們的使用者 App 需要支援 iOS 和 Android 兩個平臺,所以也需要支援多個通知系統。
Android 平臺我們使用了 FCM(Firebase Cloud Messaging)和 GCM(Google Cloud Messaging),iOS 平臺我們使用了 APNS(Apple Push Notification Service)。
每一個通知服務提供商都為不同的 App 提供了不同的 API 祕鑰和令牌。例如,GoLife 和 Gojek 使用的 FCM API 祕鑰就不一樣。
03 一個使用者多個裝置我們允許一個使用者同時登入多個裝置,所以通知需要被推送給使用者已登入的所有裝置上,這就存在之前的兩個問題:
使用者可以在單個裝置上登入多個 App(Gojek 和 GoLife);使用者可能會登入多個裝置,每個裝置需要使用不同的通知服務提供商。例如,使用者可以在 Android 裝置和 iOS 裝置上登入 Gojek。04 多個需要推送通知的服務Gojek 採用了微服務架構,我們想要讓每個服務都能推送通知,不需要操心多裝置和多服務提供商問題。
為了解決上述問題,並儘可能保持 API 簡單,我們的通知系統被分為三個元件:
通知伺服器——提供通知推送 API,並將通知推送到作業佇列中;令牌儲存——儲存已登入使用者的裝置和裝置令牌資料;通知處理器——處理作業佇列中的訊息,並將訊息傳送給通知服務提供商。
每個元件都解決了上述的一部分問題,接下來,我們來深入介紹這些元件。
(1)令牌儲存
使用者在登入 App 後,App 會使用裝置令牌和 App ID 呼叫令牌儲存 API。
令牌儲存用於決定向使用者的哪些裝置推送通知。
(2)通知伺服器
這是 HTTP 伺服器,提供用於推送通知的 API。
為了簡單起見,API 要求把使用者 ID 和 App ID 放在 HTTP 頭部,把通知資訊放在請求體裡:
POST http://<base_url>/notificationuser_id: <user_id>application_id: <application_id>{ "payload": {}, "title": "You driver is here", "message": "Please meet your driver at the pickup point"}
伺服器從令牌儲存獲取所有的使用者裝置資訊,然後為每個使用者裝置安排一個排程作業。
通知伺服器為系統提供了外部介面,需要推送通知的服務只要通過使用者 ID 來呼叫它的 API,剩下的事情由通知服務負責處理。
(3)作業佇列
我們使用 RabbitMQ 作為作業佇列,併為每一種 App ID 和通知型別建立了單獨的佇列。
分配單獨的佇列是很重要的,因為我們要為每一種 App 和通知型別做好故障隔離。例如,如果 com.gojek.app 的 FCM 令牌過期,就不會影響到 com.gojek.life 或者 com.gojek.driver.bike 的作業。
(4)通知處理器
處理器程序從作業佇列里拉取訊息,把它們傳送給對應的通知服務提供商。
為了保持程式碼簡單,並能夠支援不同的服務提供商,我們定義了統一介面:
type PushService interface { Push(ctx context.Context, m PushRequest) (PushResponse, error)}
Push 方法接收一個請求物件,並返回一個響應物件。
請求物件包含了與接收方和通知(比如過期時間、標題和文字)有關的資訊。
type PushRequest struct { DeviceID string Title string Message string Payload map[string]interface{} // 其他引數}
響應訊息裡包含了通知是否傳送成功的資訊:
type PushResponse struct { Success bool ErrorMsg string}
然後為不同的服務提供商實現介面。例如,FCM 和 APNS 對應的實現看起來像下面這樣:
type FCMProvider struct { // 配置資訊,比如 API 令牌和 URL 端點}func (p *FCMProvider) Push(ctx context.Context, m queue.Message) (notification.PushResponse, error) { // 傳送通知給 FCM 伺服器}type APNSProvider struct { // 配置資訊,比如 API 令牌和 URL 端點}func (p *APNSProvider) Push(ctx context.Context, m queue.Message) (notification.PushResponse, error) { // 傳送通知給 APNS 伺服器}
通知處理器負責選擇對應的通知服務提供商,並將訊息傳送給它們。
05 結論在面對這些挑戰時,我們找出其中的一些常用模式,把它們抽離成不同的服務,將一個相對複雜的問題變成了一系列簡單且易於管理的服務。
每當一個核心邏輯需要不同的實現時,我們就把它抽離成單獨的服務:
多個裝置問題通過令牌服務來解決;多個 App 問題通過統一的通知伺服器介面來解決;多個通知服務提供商通過單獨的作業佇列和通知處理器來解決。最終,我們構建了一個每小時能夠處理 1 百多萬個推送通知的系統。
作者:架構文摘原文連結:https://juejin.im/post/5dfc3b61f265da33d3620b5b