小米11.11:海量數據壓力下的推送服務
作者 臧秀濤
11. 11 大促,隨著移動端業務量的急劇提升,像小米推送這樣的基礎服務也經受了巨大的考驗。11 月 12 日,小米的項目總監汪軒然在微博上宣布,“小米推送服務共發出 9.65 億條消息,平均每分鐘發送 67 萬條。更值得一提的是,后臺監控顯示,推送服務后臺系統在全天運作非常平穩,沒有任何卡頓擁堵現象,讓各種促銷、返利、訂單更新消息第一時間觸達用戶。”
汪軒然,2007 年畢業于清華大學計算機系,后加入微軟亞洲工程院,曾參與 WP7 上的瀏覽器的開發。2010 年 7 月加入小米,曾擔任米聊安卓團隊的團隊主管,現在在小米任項目總監,負責小米的開發者服務,掌管推送服務、統計服務和移動廣告聯盟三大業務,旨在為小米搭 建一個移動 App 業務的互聯網生態圈。
我們聯系了汪軒然,就小米推送服務的架構、特點、性能等問題對他進行了采訪,以下內容根據本次采訪整理而成。
基礎技術架構
協議是推送服務的核心。小米推送服務所采用的協議是由之前的米聊演變過來的,而米聊從一開始就選擇使用 XMPP 協議,之后開發團隊對 XMPP 協議做過幾輪精簡和重構。現在 XMPP 部分只是作為一個數據的傳輸層,之上跑著各種獨立的業務,每個業務稱為一個“channel”;每個 channel 上跑的數據格式可以是不一樣的。消息推送服務是其中一個 channel,這個 channel 上傳輸的數據是通過 Thrift 進行二進制化的協議格式。
再來看一下小米推送服務的服務端架構。下圖是后臺服務端的一個基本架構圖。整個服務端包含如下幾層:
- XMPP 前端:用于維護跟客戶端之間的長連接,使用 EJabberd 項目來處理來自客戶端的 XMPP 請求,同時通過 XMQ 模塊來處理推送服務特有的 XMPP 消息協議。
- 中間層:業務邏輯層,主要用于將消息請求異步化、創建和維護消息隊列、以及處理客戶端的一些命令請求(注冊、設置別名、設置 topic 等)。
- HTTP 前端:這一層負責對接來自第三方 App 的服務器的發消息的 HTTPS 請求,以及來自客戶端生成賬號的 HTTPS 請求。
再就是數據存儲,這里采用了小米的統一 HBase 存儲,同時還使用 MySQL 來保存一些量不大,但需要復雜過濾條件的數據(topic 等),并且為了降低對 HBase 的壓力,中間還加了一層 Redis 作為緩存。
最后看一下客戶端架構。客戶端 SDK 主要包含兩個層次:SDK 層和 PushService 層。前者提供了面向 App 接入的接口、回調方法以及對 Thrift 的數據進行反序列化的處理邏輯;后者用于維護 XMPP 長連接和收發消息。兩層之間使用 Intent 方式來傳輸數據。值得一提的是,在 MIUI 系統上,PushService 層是系統共用的,即 MIUI 系統提供了一個統一的 PushService 管理模塊,不需要每個應用單獨啟動自己的 PushService。
功能實現
小米推送服務支持單發和群發消息兩種推送方式。單發消息支持針對 regID 和別名兩種方式,regID 是小米推送服務后臺根據設備標識 +appID+ 時間戳生成,為了減少設備碰撞概率,設備標識我們采用的依據是 imei+AndroidID+build 序列號。別名是 App 在客戶端設置上報的,便于應用將自己的設備/用戶標識符同我們的 regID 作關聯,這樣 App 就不需要在后臺維護 regID 跟設備/用戶的對應關系了。群發消息采用打標簽的方式來區分,客戶端和服務端都可以給指定設備設置標簽,發消息的時候,只需選取指定標簽發送即可,小米推 送后臺會將標簽所對應的設備展開。一個標簽支持的設備數無上限。
那小米推送服務的穩定性是如何保證的呢?小米推送服務采用多機房方案,平時流量均攤,一旦某個機房出現故障,流量無縫切換到其它機房,并且單個機房的容量能保證提供無損服務。目前是雙機房部署,預計明年會擴展第三個機房。
安全性也是小米推送服務重點考慮的一個因素。數據傳輸過程中,得益于推送服務采用的雙層協議方案,消息會采 取雙重加密,第一重是 XMPP 傳輸層,保證數據在網絡傳輸的過程中不會被篡改、監聽。第二重是在 Thrift 二進制層,用以保證消息到達 Service 之后,通過 broadcast 發送給 App 進程的過程中不會被截獲和偽造。第二重加密往往會被其它第三方推送服務忽略,但其風險同樣很大。
性能指標
11. 11 大促,所面對的請求量是在小米推送服務的設計容量之內的,目前設計和機器規模可以支持峰值每分鐘 1000 萬條消息;平時業務量至少每分鐘 40 萬,峰值每分鐘 600 萬條消息。
推送消息量平時波動很大,所以開發團隊準備著流量隨時可能忽增 200% 的情況,并在線下做好壓力測試和優化;如果流量特別大,還有以下應對措施
- 異步排隊處理,此時消息送達時間可能會比平時稍慢,但不會對整個系統有太大沖擊;
- 消息有優先級,廣播消息會以低優先級處理;
- 限流,控制開發者發送消息的頻率;
- 擴容,如果機器負載過高或者某個服務有瓶頸,可以很快速地增加機器,部署服務,增強系統處理能力。
小米推送服務所經歷的重構
軟件系統在開發和演進過程中,經常會經歷較大規模的重構。小米推送服務有兩次比較大的重構。
一是開發語言從 Erlang 轉為 Java。 小米原來的消息系統是使用 Erlang 開發的,所以推送系統的第一版也是基于 Erlang;但是 Erlang 的社區不夠活躍,開發人員很難找,學習曲線陡,支持工具和類庫少,所以后來開發團隊選擇了使用 Java 重新開發;遷移到 Java 后,對開發人員的要求降低,各種工具和類庫較多,大大提高了開發效率。
二是無處不在的 Cache。客戶端使用小米推送服務的 SDK,開發者使用 API 的情況千變萬化,很多場景是意料之外的;需要對調用頻繁的業務添加 Cache,盡可能在本地進程內處理;例如,對于客戶端調用 API 設置別名和訂閱 topic,先檢查 Cache 是否已經設置過,只有沒有設置才往后端服務發送;優化后,后臺服務的業務壓力大大減少。
在開發小米推送過程中的一些感悟
- 服務要支持水平擴展,盡可能實現為無狀態,或者使用一致性哈希進行劃分;方便擴容,可以保證即使系統暫時有性能瓶頸也能通過加機器解決。
- 監控先行,能夠很方便地采集、分析服務器的負載和業務的請求量、percentile、slow log,能夠清楚了解到系統的瓶頸,有針對性地改進。
- 不要過早優化,先實現功能并盡快上線,根據監控數據對關鍵地方進行優化。
- 敏捷開發,快速迭代,日拱一卒,每天都有簡短的站立會議,能夠迅速響應變化,持續改進系統。