雲巴:基於MQTT協議的實時通訊編程模型

概要

有人常問,雲巴實時通訊系統到底提供了一種怎樣的服務,與其餘提供推送或 IM 服務的廠商有何本質區別。其實,從技術角度分析,雲巴與其它同類廠商都是面向開發者的通訊服務,宏觀的編程模型都是大同小異,真正差別則聚焦於產品定位,業務模式,基礎技術水平等諸多細節上。本文暫不討論具體產品形態上的差別,着重從技術角度淺談實時通訊的編程模型。html

什麼是實時通訊

「實時」(realtime) 一詞在語義層面上隱含着對時間的約束(real-time constraint),在工程上,咱們習慣對「須要在必定時間內」 完成的操做稱爲「實時操做」。一般,實時可細分爲 「軟實時」(soft realtime),「準實時」(firm realtime)和 「硬實時」(hard realtime)。它們之間的差別,簡單來講,就是對沒法在指定時間區間內(deadline)完成事務的容忍程度。維基百科上對這三者有以下解釋前端

  • Hard – missing a deadline is a total system failure.git

  • Firm – infrequent deadline misses are tolerable, but may degrade the system's quality of service. The usefulness of a result is zero after its deadline.github

  • Soft – the usefulness of a result degrades after its deadline, thereby degrading the system's quality of service.算法

假如咱們把沒法按時完成任務(missing a deadline)稱爲異常事件,那麼硬實時系統沒法容忍異常事件;準實時系統則可容忍極少許的異常事件,但超過必定數量後系統可用性爲 0;軟實時系統可容忍異常事件,可是每發生一次異常事件,系統可用性下降。編程

綜上所述,咱們能夠舉例:後端

  • 火星上的無人探測器是硬實時系統,由於一次異常事件就極有可能致使探測器不可用,同理可類推核電站的監控系統,軍用無人機系統,遠程導彈的導航系統等一系列軍工產品;設計模式

  • 金融交易系統是準實時系統,此類系統可容忍極少數的交易故障,一旦故障次數增長,系統就會陷入崩潰狀態;緩存

  • 短信 / 手機推送 / 電商購物等都是軟實時系統。對於此類系統,用戶均可以容忍異常事件,可是太多的異常事件則會大幅下降系統可用程度,用戶體驗急劇下滑。安全

就目前來講,絕大多數互聯網產品(甚至能夠說是 100%)都是軟實時系統。雲巴實時通訊系統的目標則是要作一個高可用的軟實時系統

一個最簡單的實時通訊編程模型

在軟件工程中,不少複雜的項目其實均可以用一個很是簡潔的模型來歸納。正如愛因斯坦所說的:「一切都應該儘量地簡單,但不要太簡單」(Everything should be made as simple as possible, but not simpler)。雖然這是描述物理世界的經驗之談,但一樣適用於計算機領域,將物理世界的關係投射到某種人爲語言(物理公式/計算機編程語言),其規律其實都是共通的。

讓咱們假設這麼一個簡單的場景:對 10 個客戶端發送一條消息

這個需求其實能夠用僞碼錶示爲:

for (i..10) {
    send_message(get_socket(i))
}

若是下圖所示:

pic

在這個簡單的需求下,咱們只須要讓這 10 個客戶端分別跟服務器創建 TCP 鏈接(本文暫時只討論 TCP 協議),而後遍歷地發送消息便可。顯而易見,這是一個 O(N) 複雜度的邏輯。

基於這個簡單的模型,咱們能夠認爲一條消息從發出到接收,有如下幾個延時:

  • 網絡延遲 ,通常是一個較爲穩定的值,好比從北京到深圳,ping 延遲大約爲 40 ms 左右;

  • 系統處理延遲,較之網絡延遲,該值變化幅度較大,且可能因處理請求數的增長而急劇增大;

雲巴實時通訊系統以 200 ms 延遲做爲總延遲標準,也就是說,假如網絡鏈路是從北京到深圳,除去網絡延遲的 40 ms,要想達到 200 ms 的通訊時間,系統延遲必須小於 160 ms。

能夠想象,當客戶端數量達到必定數量級(好比百萬級別)時,以上系統模型的實時性將面臨極其嚴峻的考驗。

分而治之

在海量用戶下保持穩定的實時性,其實不少時候就只有一個手段:分而治之

圖 1 表示的是單機處理狀況。當單機的處理能力,帶寬都沒法應對客戶端數量急劇增長的時候,咱們就必須將線路進行分割。並且圖 1 只體現了推送的意圖(單向),但通訊每每是一個雙向的概念,綜上,咱們將 圖 1 改爲下面的 圖 2

pic

這樣每臺機器就能夠處理符合其當前水位的鏈接。

在現實開發中,咱們可能不只僅知足於一個如此簡單的消息系統,咱們可能想要有離線消息,數據統計,數據緩存,限流等一系列操做,因此咱們還能夠再優化一下架構:

  • 將總體架構劃分紅業務邏輯層和數據存儲層;

  • 數據存儲層又能夠根據存儲數據類型的不一樣來進一步劃分;

  • 前端能夠單獨劃分一個網絡接入層;

  • 數據包的流向能夠用 MQ 來串聯;

這樣咱們能夠獲得如下的圖 3:

在這個模型中,網絡接入層和消息業務邏輯層總體上應該是一個 stateless 的模塊,能夠較爲輕鬆地作橫行擴展。存儲層做爲一個有狀態的模塊,想要作到橫行擴展是一件很不容易的事情。若是撇開這點來看,至此,這個模型理論上在應對海量用戶的場景下應該是有效的。

通訊協議和技術棧的選擇

作一個消息系統,不可避免地要涉及到對通訊協議的選擇。咱們在對通訊協議的選擇上,遵循如下幾個原則:

  • 協議儘量精簡輕量,由於在系統設計之初咱們就考慮了對物聯網的支持,省電,節約流量都是目標之一;

  • 通用性好,擴展性強,方便後期作特性開發;

  • 協議在業界被普遍承認,且儘量多的有不一樣語言的開源實現,以方便不一樣技術棧的客戶作集成;

綜上,咱們沒有從新自定義一份通訊協議,而是選擇了基於長鏈接MQTT。從不少角度來看,MQTT 很是適合作消息總線的通訊協議,並且協議棧也足夠輕巧和易於實現。雲巴實時消息系統傳輸的消息體積較小(通常小於 4 KB),好比控制信號,普通聊天信息等。就這點上,針對物聯網設計的 MQTT 有着自然的優點。後面,在不斷地研究中咱們又發現,MQTT 其實不只僅適用於物聯網場景,在不少要求低延遲高穩定性的非物聯網場景也一樣適用(好比手機端 app 推送,IM,直播彈幕等)。

從前面幾個章節咱們看到,雲巴消息系統是一個典型的 IO 密集型系統。在出於開發效率和穩定的考慮下,咱們選了 Erlang/OTP 做爲主力開發語言。Erlang/OTP 做爲一門小衆開發語言(不管是國內仍是國際),在應付這類 IO 密集型系統上,有着得天獨厚的優點(可參考 RabbitMQ 這個基於 Erlang/OTP 的著名開源項目):

  • 基於 actor 的進程建立模型,能夠爲每一個數據包建立一個 Erlang 處理進程,充分利用多核;

  • OTP 的開發框架抽象了分佈式開發的許多細節,使得開發者在很小的心智負擔下就能輕鬆快速地開發出功能原型;

  • Erlang/OTP 充分運用了容錯思想,應對異常不是防,而是容,不少時候咱們寫出一些安全邏輯上有漏洞的代碼,在 Erlang/OTP 上竟然也能工做得好好的;

隨着不斷深刻地使用 Erlang/OTP, 其性能問題也漸漸凸顯出來。咱們發現,當客戶端請求量增長的時候,用 Erlang/OTP 寫出的模塊垂手可得地就能夠將 CPU 跑滿,從而讓當前實例超負荷運轉。不少時候出於成本上的考量,咱們沒法選擇更多核數的機器來提高 Erlang 虛擬機運行的性能(此點未明確驗證過),因此只好選擇適度增長服務處理實例來緩解壓力。

不過,經過對業務模塊更細粒度的劃分,咱們能夠將一些核心的小模塊用 C/C++ 語言改寫,在必定範圍的複雜度內,能夠有效提高總體處理性能。這也是咱們接下來優化核心系統的思路之一。

MQTT 的 Pub/Sub 模型與高可用 KV 存儲

MQTT 協議採用的是 Pub/Sub 的編程模型。其中有三個比較關鍵的動做:publishsubscribeunsubsribe。經過前面幾個章節的討論,咱們又能夠獲得這麼一個場景:

假如存在一個訂閱量巨大的 topic(百萬級),如何在單次 publish 中保證明時性 ?

其實,解決思路跟以前的場景是一致的:分而治之。咱們必須經過某種策略對 topic 進行分片,而後將分片分發到不一樣的 publish 模塊上進行處理。在必定的算法複雜度下,這個問題理論上是能夠被有效解決的。因而,topic 的分片策略就成了高性能 publish 的關鍵。其實,若是想採用 MQTT 作海量消息系統,訂閱關係的管理必定是沒法繞開的大問題。它主要有如下幾個設計難點:

  • 若是採用 KV 方式存儲,如何設計數據結構 ?同上,咱們要怎樣去設計一種高效的 topic 分片存儲策略;

  • 訂閱關係的管理是 MQTT 消息系統的核心模塊,假如這個存儲模塊失效,就一定會致使消息通訊失敗,從而讓客戶端收不到消息,這就必需要求這個模塊必定是高可用的,也就意味着咱們必須構建一個高可用的 KV 存儲集羣,該集羣要能容忍必定程度的節點失效;

  • 冷熱 topic 要有淘汰機制,要有必定策略將不活躍的 topic 按期淘汰到磁盤以節約內存容量;

  • KV 存儲集羣要能高效地動態擴容;

在很長一段時間的實踐中,咱們採用過好幾種 KV 存儲的集羣方案,踩了很多坑,最後仍是決定本身造輪子來開發一個高可用的 KV 存儲模塊。不過這又是一個很大的話題,咱們將在後續博客中具體闡述咱們的作法。

缺陷與不足

在團隊發展初期,因爲人力和時間等種種因素,咱們把業務邏輯模塊開發成了一個巨大的單體架構應用。在團隊規模較小的狀況下,單體架構的應用確實較好維護和開發,但隨着新人的加入,單體架構則嚴重製約着特性開發和性能優化。從架構層面上來看,合理地劃分更細粒度的模塊,在性能和可維護性上採用微服務(microservice)設計模式,成了咱們將來優化系統的方向之一。

總結

軟件工程上有「沒有銀彈」(No Silver Bullet)這條金科玉律,用戶選擇雲服務商亦是如此,絕對沒有完美的第三方雲服務商,每一家均可能存在明顯的優勢和缺陷。用戶必須從本身應用場景和痛點出發,選擇合適的後端服務。雲巴將會在本身產品的核心競爭力上持續發力,精打細磨,吸收行業內的高效實踐經驗,打造出更加優秀的高可用實時通訊系統。

相關文章
相關標籤/搜索