
這樣每臺機器就可以處理符合其當(dāng)前水位的連接。
在現(xiàn)實開發(fā)中,我們可能不僅僅滿足于一個如此簡單的消息系統(tǒng),我們可能想要有離線消息,數(shù)據(jù)統(tǒng)計,數(shù)據(jù)緩存,限流等一系列操作,所以我們還可以再優(yōu)化一下架構(gòu):
將整體架構(gòu)劃分成業(yè)務(wù)邏輯層和數(shù)據(jù)存儲層;
數(shù)據(jù)存儲層又可以根據(jù)存儲數(shù)據(jù)類型的不同來進一步劃分;
前端可以單獨劃分一個網(wǎng)絡(luò)接入層;
數(shù)據(jù)包的流向可以用 MQ 來串聯(lián);
這樣我們可以得到以下的圖 3:

在這個模型中,網(wǎng)絡(luò)接入層和消息業(yè)務(wù)邏輯層整體上應(yīng)該是一個 stateless 的模塊,可以較為輕松地做橫行擴展。存儲層作為一個有狀態(tài)的模塊,想要做到橫行擴展是一件很不容易的事情。如果撇開這點來看,至此,這個模型理論上在應(yīng)對海量用戶的場景下應(yīng)該是有效的。
通信協(xié)議和技術(shù)棧的選擇
做一個消息系統(tǒng),不可避免地要涉及到對通信協(xié)議的選擇。我們在對通信協(xié)議的選擇上,遵循以下幾個原則:
協(xié)議盡可能精簡輕量,因為在系統(tǒng)設(shè)計之初我們就考慮了對物聯(lián)網(wǎng)的支持,省電,節(jié)約流量都是目標(biāo)之一;
通用性好,擴展性強,方便后期做特性開發(fā);
協(xié)議在業(yè)界被廣泛認(rèn)可,且盡可能多的有不同語言的開源實現(xiàn),以方便不同技術(shù)棧的客戶做集成;
綜上,我們沒有重新自定義一份通信協(xié)議,而是選擇了基于 長連接 的 MQTT 。從很多角度來看,MQTT 非常適合做消息總線的通信協(xié)議,而且協(xié)議棧也足夠輕巧和易于實現(xiàn)。云巴實時消息系統(tǒng)傳輸?shù)南Ⅲw積較小(一般小于 4 KB),比如控制信號,普通聊天信息等。就這點上,針對物聯(lián)網(wǎng)設(shè)計的 MQTT 有著天然的優(yōu)勢。后面,在不斷地研究中我們又發(fā)現(xiàn),MQTT 其實不僅僅適用于物聯(lián)網(wǎng)場景,在很多要求低延遲高穩(wěn)定性的非物聯(lián)網(wǎng)場景也同樣適用(比如手機端 app 推送,IM,直播彈幕等)。
從前面幾個章節(jié)我們看到,云巴消息系統(tǒng)是一個典型的 IO 密集型系統(tǒng)。在出于開發(fā)效率和穩(wěn)定的考慮下,我們選了 Erlang/OTP 作為主力開發(fā)語言。Erlang/OTP 作為一門小眾開發(fā)語言(無論是國內(nèi)還是國際),在應(yīng)付這類 IO 密集型系統(tǒng)上,有著得天獨厚的優(yōu)勢(可參考 RabbitMQ 這個基于 Erlang/OTP 的著名開源項目):
基于 actor 的進程創(chuàng)建模型,可以為每個數(shù)據(jù)包創(chuàng)建一個 Erlang 處理進程,充分利用多核;
OTP 的開發(fā)框架抽象了分布式開發(fā)的許多細(xì)節(jié),使得開發(fā)者在很小的心智負(fù)擔(dān)下就能輕松快速地開發(fā)出功能原型;
Erlang/OTP 充分運用了容錯思想,應(yīng)對異常不是防,而是容,很多時候我們寫出一些安全邏輯上有漏洞的代碼,在 Erlang/OTP 上居然也能工作得好好的;
隨著不斷深入地使用 Erlang/OTP, 其性能問題也漸漸凸顯出來。我們發(fā)現(xiàn),當(dāng)客戶端請求量增加的時候,用 Erlang/OTP 寫出的模塊輕而易舉地就可以將 CPU 跑滿,從而讓當(dāng)前實例超負(fù)荷運轉(zhuǎn)。很多時候出于成本上的考量,我們無法選擇更多核數(shù)的機器來提升 Erlang 虛擬機運行的性能(此點未明確驗證過),所以只好選擇適度增加服務(wù)處理實例來緩解壓力。
不過,通過對業(yè)務(wù)模塊更細(xì)粒度的劃分,我們可以將一些核心的小模塊用 C/C++ 語言改寫,在一定范圍的復(fù)雜度內(nèi),可以有效提升整體處理性能。這也是我們接下來優(yōu)化核心系統(tǒng)的思路之一。
MQTT 的 Pub/Sub 模型與高可用 KV 存儲
MQTT 協(xié)議采用的是 Pub/Sub 的編程模型。其中有三個比較關(guān)鍵的動作: publish, subscribe 和 unsubsribe 。通過前面幾個章節(jié)的討論,我們又可以得到這么一個場景:
假如存在一個訂閱量巨大的 topic(百萬級),如何在單次 publish 中保證實時性 ?
其實,解決思路跟之前的場景是一致的: 分而治之 。我們必須通過某種策略對 topic 進行分片,然后將分片分發(fā)到不同的 publish 模塊上進行處理。在一定的算法復(fù)雜度下,這個問題理論上是可以被有效解決的。于是,topic 的分片策略就成了高性能 publish 的關(guān)鍵。其實,如果想采用 MQTT 做海量消息系統(tǒng),訂閱關(guān)系的管理一定是無法繞開的大問題。它主要有以下幾個設(shè)計難點: