首頁>科技>

可以想象的是,我們每天推送的通知數量有多大——大概每小時 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

最新評論
  • 整治雙十一購物亂象,國家再次出手!該跟這些套路說再見了
  • 新能源車主看了這些,安心溫暖過寒冬