消息中臺的全量消息實現

消息中臺爲百度 App 以及廠內百度系產品提供即時通訊的能力,提供包括私聊、羣聊、聊天室、直播彈幕等用戶溝通場景,並幫助業務通過消息推送觸達用戶。百度 App 存在需要以『低用戶打擾』的形式觸達全量用戶的場景,而現有基於用戶『私有信箱』通知拆分的機制,很難低成本、高時效的滿足該場景訴求。基於上述問題,本文介紹了現有消息系統的主要組成,對比多種實現方案的差異,提出以『公有信箱』通知讀擴散的方式,低成本、高時效的實現全量用戶通知推送。

01 全量消息提出背景

百度 App 存在需要觸達全量用戶的訴求,比如:2022 年 12 月 7 日解除疫情管控結束後,將經過篩選的官方政策解讀、專題彙總、知識科普、實用工具類介紹等信息,通過官方號『百度小助手』下發觸達到百度 App 用戶,來有效體現人文關懷,提高用戶粘性。

1.1 全量消息訴求

在以消息服務進行全量觸達(即全量消息)時,期望能夠滿足:

在觸達範圍上,希望儘量擴大用戶觸達範圍,包括百度 App 月活用戶、以及非月活用戶但是近期新註冊或登錄的用戶(根據 2022 年 12 月對外公開數據,百度 App 月活 6 億 + 用戶);在時效上,一次全量觸達,希望短時間內完成(比如小時級、甚至分鐘級),搶佔時效性;在用戶打擾方面,消息觸達不能給用戶帶來較大的打擾,每次消息下發,只觸達一次,不能重複打擾用戶,但是需要保留回訪入口,滿足用戶二次查看的訴求。

1.2 現有技術痛點

我們現有 IM(即時通訊)服務中,每個 IM 用戶對應一個用戶信箱。基於現有服務,如果想完成全量用戶的消息觸達,需要把消息推送到每個用戶的信箱。完成 6 億 + 的消息寫入(假定每條佔用存儲 4KB,每秒寫入 2W 條消息),在消息寫入時效性,以及存儲資源消耗上,都是很難接受的。且現有的基於用戶私有信箱的方案,在同時支持多條全量消息的場景下,擴展性也較差。

基於上述背景和技術痛點,我們抽象基於公共信箱的全量消息實現:在特定業務場景下通過消息服務,低成本、高時效的給全量用戶推送內容一致的通知消息。

02 現有消息系統介紹

在介紹基於公共信箱(信箱的實現方式,該信箱爲 IM 用戶公有)的全量消息實現之前,先介紹一下目前消息系統的現狀,包括消息系統的組成、通知拉取模式、用戶信箱等。

2.1 消息系統組成

普通用****戶的直觀體驗上看,一個 IM 系統可以包括如下幾個元素:用戶主體、用戶賬號、賬號關係、聊天會話、聊天消息。『用戶主體』具有『用戶賬號』,『用戶主體』具有頭像、暱稱等用戶屬性,『用戶主體』通過『用戶賬號』登錄 IM 系統,進行聊天;賬號之間的關注、屏蔽、免打擾等構成『用戶關係』;通過用戶之間的互動環節可以產生『聊天消息』;聊天記錄構成了一個『聊天會話』。

集成消息服務的業務方角度看,一個 IM 系統可以包括消息客戶端(消息客戶端 UI 組件、消息 SDK)和消息服務端。IM 消息可以作爲一種服務,嵌入到各業務系統中,爲業務系統提供『實時交互』能力。業務通過集成 IM 服務,提升其用戶體驗。如下爲一個集成了 IM SDK 的業務架構圖。業務 App 集成 IM SDK,通過 IM SDK 與 IM Server 交互,完成用戶上行通訊能力。業務 App Server 通過與 IM Server 交互,完成通知下行觸達用戶。

使用場景來看,消息包括『私信消息』(包括用戶上下行消息)、『通知消息』(業務方給用戶推送的下行消息)、『羣聊』、『聊天室』、『直播間彈幕』等。

2.2 消息的通知拉取模式

IM 消息系統,採用 通知拉取(notify-pull) 模式來感知新消息、拉取新消息。IM SDK 登錄時,與 IM 服務端建立長連接(LCS, Long Connect Service),用戶有新的消息時,通過長連接下發 notify,實時通知用戶的 IM SDK。實時 notify 不寫用戶信箱,因爲 noitfy 不是消息,而可以理解爲提醒在線用戶有新消息的信號,IM SDK 根據這個信號,來服務端拉取消息。業務方 server 或者其他用戶給該用戶發送消息後,經過 IM 業務處理模塊,把消息寫入接收者信箱,IM Server 會根據用戶的登錄和路由信息,給消息接收者(私信場景下也包括『消息發送者』,用於消息的多端同步)發送新消息 notify,接收到 notify 的 IM 設備,通過 IM SDK 來 IM Server 端拉取 (pull) 消息。

2.3 用戶信箱介紹

爲了暫存尚未拉取到 IM SDK 本地的離線消息,需要對消息進行服務端存儲,而消息的離線存儲通過消息信箱服務完成。目前 IM 用戶消息信箱主要包括用戶私有信箱、羣公共信箱(非下文提到的用戶公共信箱)、直播間彈幕 mcast 等。用戶信箱通過『消息所屬應用』+『IM 標識用戶的唯一 ID』來標識。就一條消息而言,消息參與者有『消息發送者』和『消息接收者』,消息收發雙方的信箱都是相互獨立的(假設發送方刪除了自己信箱的某一條消息,不會影響消息接受者信箱的消息)。對於有查看歷史消息訴求的一方來說,消息需要入該方的信箱:比如用戶之間的私信(點對點聊天)消息需要入發送者和接收者的信箱,而對於全量通知場景,消息不需要存儲發送者信箱,而只需要存接收者的信箱。而用戶的信箱排序,是基於信箱 Timeline,即消息在信箱內部基於時間線存儲,每條消息對應一個 unix 微秒時間戳(如第一條消息 1679757323320865),用戶進行信箱拉取時,基於時間範圍正序或者逆序拉取,如下爲信箱 timeline 的示例:

△信箱 timeline

用戶信箱中的每一條消息記錄都包含『消息 ID』、『消息用戶標識』、『消息通用屬性』、『消息業務屬性』四個主要部分。消息 ID 爲 unix 微秒時間戳,不需要全局唯一,只需要特定用戶信箱範圍內唯一即可。消息用戶標識包括 from_uid、to_uid、contacter。消息通用屬性包括 create_time、expire、is_read。消息業務屬性包括 category、type、priority、business_type、app_id、msgkey、content 等。如下爲一條消息記錄示例:

△消息記錄示例

03 全消息實現

3.1 全量消息推送方案分析

目前消息推送機制中,主要支持:單播(消息推送方式,每次給一個用戶推送一條消息)、批量單播(每次給小範圍用戶推送消息,比如 30 個)、廣播(基於關注關係的推送,如給全量粉絲推送),上述三種消息推送機制推送的消息,均需要存儲服務端的用戶私有信箱。爲了完成百度 App 6 億 + 月活用戶(_月活數據來源:2022 年 12 月百度 App 公開月活數據,_https://baijiahao.baidu.com/s?id=1758522783976467912&wfr=spider&for=pc)的消息推送,有幾種可選的方案。

3.1.1 全流程從通知入口推送

①該種方式下,需要獲取全量的月活用戶列表,經過 IM Server 推送入口,給每一個用戶推送疫情相關通知。該通知寫入到用戶信箱,若用戶在線,在實時拉取該通知;若用戶離線,再下次登錄 IM 服務時,拉取離線通知。該種方案下,推送行爲會覆蓋 IM 的全流程,推送的通知會進入每個月活用戶的私有信箱,服務壓力大。其中增量用戶不會收到通知推送(這裏增量用戶指的是不在月活用戶列表的用戶)。

3.1.2 跳過通知入口直接寫信箱

②跳過 IM 消息推送流程中的中間環節,直接把通知消息寫入用戶信箱。由於跳過了中間流程,直接寫入信箱,通知寫入速度主要取決於信箱底層存儲的壓力承受情況。該種方案下,同①方案一樣,無法給用戶發送實時通知,依賴用戶 IM SDK 的主動消息拉取(斷鏈後重新登錄 / 新消息提醒拉取),無法給增量用戶發送通知。該方案由於跳過中間環節直接寫信箱,風險較大,無法直接提供給業務方使用,不建議如此操作。

3.1.3 公有信箱實現機制

③公有信箱機制,把通知消息寫入『公共信箱』,在用戶消息拉取時,合併『用戶私信信箱』+『公共信箱』的消息。

3.1.4 三種方案比較

方案①②都是寫擴散方式,基於現有『用戶私有信箱』的機制,把通知消息寫入每個接收通知的用戶私有信箱。方案②與方案①的差別主要是跳過了消息中間流程,可以避免因爲中間環節負載瓶頸導致整體消息寫入速度過低。方案③是讀擴散方式,消息不用再寫入接收通知的用戶私有信箱,而只需要在公共信箱存儲一份,在用戶拉取消息時,實時拉取公共信箱的消息。方案③中可以採用內存緩存方案,解決對公共信箱的讀壓力。本質上來說,方案③與方案①②相比,是用讀成本(CPU)換寫成本(存儲)。

3.2 基於公有信箱的全量消息實現

基於上述方案③的思路,我們進行基於公有信箱的全量消息設計與實現。該種方案中,包含兩個主要流程:『全量消息的管理』和『用戶私有 + 公有信箱的拉取』。

3.2.1 全量消息的管理

全量消息管理主要分爲運營 O 端操作平臺(複用運營消息平臺),以及全量消息處理服務(複用 IM 服務的連接層、邏輯處理層、信箱代理、信箱處理)。運營 O 端平臺爲運營同學提供可視化界面,可以對全量消息進行編輯、預發佈、發佈、修改、停止、撤回等操作;接入層對接運營 O 端,進行參數校驗、轉發 IM 後端邏輯處理模塊;邏輯處理層進行全量消息的創建、修改、停止、刪除、撤回等邏輯操作;信箱代理層複用 IM 服務的信箱 CRUD 操作;信箱存儲層公共信箱的底層存儲。

△全量消息管理流程

3.2.1 用戶信箱拉取

用戶通過 IM SDK,以長連接的方式,在邏輯處理層進行消息拉拉取。在用戶拉取信箱消息時,需要對『用戶個人信箱』和『公有信箱』進行合併。由於每次用戶信箱拉取,都需要進行信箱的合併拉取。

公共信箱內存緩存機制

百度 App 的 IM 用戶,在 IM SDK 登錄時需要拉取信箱中的消息。每次消息拉取時,需要檢查公共信箱中是否有消息。因此,公共信箱需要能抗住日常和峯值流量(拉取峯值爲 4.7Wqps)。爲了防止流量擊穿,流量打到底層的持久化公共信箱 MYSQL 存儲,我們設計了基於內存的公共信箱緩存機制。同時公共信箱內容變化時,也要實時(或者在能容忍的範圍內做到準實時)變更內存緩存信箱中的消息,我們採用 Bthread 定期輪詢持久化公共信箱,更新內存公共信箱,輪詢間隔可配置(比如設置 1 秒)。

分級發佈機制

同時,在邏輯層實現白名單機制,支持全量消息在『預發佈』狀態下,僅對白名單用戶可見,從而達到分級驗證的效果。白名單的用戶列表通過邏輯處理成的配置加載,也支持通過 CURL 請求動態修改白名單的配置。

3.3 基於公有信箱的全量消息實現需要解決的問題

以公有信箱的方案實現全量消息,需要解決如下問題:

04 全量消息公共信箱實現的優缺點

以公共信箱的方式,實現全量消息分發,具有:**『分發速度快』、『資源成本低』**的特點。

公共信箱的方式也存在一定的侷限性:

1、不適用於個性化要求高的場景:由於消息在公共信箱只存儲一份,下發消息內容固定,無法很大程度下,下發個性化消息(當然也不是一定無法下發個性化的消息,可以通過在公共信箱存儲消息模板,根據拉取消息的用戶 ID 獲取個性化信息,在消息拉取時,臨時拼裝消息,這樣就增大了消息拉取時的代價)。

2、不適用於實時消息提醒場景

①從業務場景上看,全量消息優先級低,不需要在全量生效的瞬間讓用戶感知;

②從實現上看,全量消息實時消息提醒成本高(因爲實時消息提醒 Notify,需要以類似單播的形式實時通知用戶。和單播的區別是,Notify 不用觸達離線用戶,也就是不用寫用戶信箱,只需實時觸達在線用戶);

③從系統壓力看,全量在線用戶均收到實時新消息提醒,會帶來信箱拉取請求的瞬時流量(手百 IM SDK 長連接峯值在線 1550W,假定新消息提醒在瞬間下發,同時在線用戶信箱拉取請求,會把 db 打掛的)。

05 全量消息的應用

全量消息目前已經在百度 App 得到應用,包括:重大通知的下發,百度 App 功能更新介紹通知,消息的撤回,後續將推廣到其他的矩陣 App 的全量通知推送場景。

22 年 Q4 宣佈疫情解封時,利用全量消息推送,低成本、高時效的完成 3 條『疫情解封專項』全量消息下發。

2rua8X

備註:三次全量消息下發,到達數據在 2 億 +,該值小於月活的 6 億 +,主要因爲幾個原因:

①本次全量消息有效期僅 3 天左右,全量消息有效期內登錄 IM SDK 的用戶纔有機會拉到全量消息;

②本次下發使用了新的消息展示模板,所以限制了拉取全量消息的百度 App 版本,只有高版本百度 App 可以拉到;

③本次全量消息,限制了僅有百度 App 登錄用戶拉取。

06 展望

前文介紹了現有消息系統,通過公有信箱低成本、高分發速度完成全量消息下發的設計、實現與應用。在全量消息應用方面,除了業務上的使用,後續也可以用於廣播消息、批量單播消息的撤回。比如由於誤操作發送了廣播消息,用戶已經把廣播消息拉到了端,並持久化到端,這是可以『以全量消息的方式,下發刪除指令』,刪除已經緩存到端的垃圾消息。

希望,通過消息系統持續不斷優化,爲更多的業務提供低成本、高穩定性的即時通訊能力。

作者:百度消息中臺團隊

來源:百度 Geek 說

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/R0UtEj0AY_9oJ8fsqTKRJA