MQTT 3.1協(xié)議非嚴(yán)肅反思錄
前言
MQTT 3.1協(xié)議在弱網(wǎng)絡(luò)環(huán)境下(比如2G/3G等)表現(xiàn)不夠好,因此才有了反思。
弱網(wǎng)環(huán)境下表現(xiàn)
手機等終端在弱網(wǎng)絡(luò)環(huán)境下丟包情況會非常明顯,連接MQTT Server成功率很低。相比單純的請求-相應(yīng)模型的HTTP,其成功率會比MQTT訂閱成功高很多。
手機終端在每次TCP斷開或斷網(wǎng)后,會即刻發(fā)起TCP重連,連接成功,會重復(fù)以前步驟依次發(fā)送連接命令(CONNECT),訂閱命令(SUBSCRIBLE),表明上看,這些過程沒有任何問題,但問題就在于從終端成功建立到服務(wù)器的連接,到發(fā)送訂閱命令,在弱網(wǎng)情況下,這個過程將會變得很昂貴:
從TCP建立開始的三次握手到完整的訂閱命令發(fā)送完畢,考慮到TCP堆棧的每次接收數(shù)據(jù)方響應(yīng)ACK,這中間終端和服務(wù)器端至少產(chǎn)生了10次數(shù)據(jù)交互。
在網(wǎng)絡(luò)變化頻繁或者不太穩(wěn)定的2G/3G網(wǎng)絡(luò)環(huán)境下,這種過程顯得有些冗長和不適應(yīng),同時會加重已經(jīng)不堪的弱網(wǎng)絡(luò)負載的負擔(dān)。
弱網(wǎng)下,在任何一個階段的執(zhí)行過程中,都有可能產(chǎn)生突發(fā)性的網(wǎng)絡(luò)中斷的問題:
-
無法成功建立TCP鏈接,或死在三次握手期間,或數(shù)據(jù)包丟失在握手之后,或客戶端連接超時過小
-
建立連接后,發(fā)送CONNECT命令后,或沒接收到TCP ACK確認包,或客戶端等待延時太小,導(dǎo)致訂閱命令交互失敗
-
發(fā)送SUBSCRIBLE命令后,但服務(wù)器端沒收到,或因為丟包,或網(wǎng)絡(luò)已斷開,導(dǎo)致發(fā)送SUBSCRIBLE命令失敗
-
成功發(fā)送SUBSCRIBLE命令后,或移動網(wǎng)絡(luò)斷開了(有些運營商針對認為HTTP的請求有超時判斷),或等待超時,導(dǎo)致訂閱失敗
TCP是無感知的虛擬連接,中間斷開兩端不會立刻得到通知,否則就用不著心跳保活機制了。
舉一個例子,線上的服務(wù)器根據(jù)日志分析,只接收到連接命令(CONNECT)但沒有后續(xù)的訂閱命令(SUBSCRIBLE)的情況,每天有上百萬級別的數(shù)量。
總之,針對低速率弱網(wǎng)絡(luò)環(huán)境,MQTT表現(xiàn)不怎么好。
改進點
業(yè)務(wù)改進點:
- 客戶端的連接超時、等待超時設(shè)大一點,兩秒太短,可設(shè)置長一些,比如10秒
- 服務(wù)器端支持在接收到用戶發(fā)送CONNECT命令后,瞬間發(fā)送一些live data/hot data(早已緩存的數(shù)據(jù)),類似于HTTP請求-相應(yīng)模型,目的嘛,一些熱數(shù)據(jù)發(fā)送給終端要趁早,越快越好(所謂出名要趁早嘛);這個需要客戶端、服務(wù)器端同時支持
協(xié)議改進點:
- CONNECT命令可變頭部包含"MQisdp"太多余了,學(xué)院派風(fēng)格嘛
- 允許在連接命令中負載(payload)中攜帶訂閱Topic字符串
- 允許在連接命令中表示上次連接訂閱的Topic發(fā)生變化否,攜帶訂閱業(yè)務(wù),雖冗余,但實用。 eg:訂閱的Topic沒有發(fā)生變化,TOPICCHANGE:0;退訂,UNSUBSCRIBE:TOPICONE;SUBSCRIBE:TOPIC_TWO
- PUBLISH、PUBACK等支持的 Message Identifier 才16位,太短,實際業(yè)務(wù)無法做到全局唯一。引入mid和業(yè)務(wù)id的映射對應(yīng)關(guān)系?那是狀態(tài),需要維護,代價還是蠻高的。業(yè)界流行看法,無狀態(tài)化的架構(gòu)才是便于橫向、豎向、縱向、四方向的擴展,呵呵。最好方式就是修改使之支持字符串形式,否則維護代價高!
- 心跳命令PINGREQ/PINGREQ可以做到一個字節(jié)傳輸,節(jié)省一個字節(jié),有些強迫癥的感覺嘛
- 低速率網(wǎng)絡(luò)需要做一些兼容和調(diào)整
有些建議看似冗余,批量或打包處理總比單個處理更高效一些、更節(jié)省資源,弱網(wǎng)絡(luò)環(huán)境要求交互要盡可能的少,數(shù)據(jù)嘛要的是瞬間抵達,越快越好。
嚴(yán)格的分層和業(yè)務(wù)解耦,會導(dǎo)致性能問題。好比當(dāng)前Linux內(nèi)核的TCP/IP網(wǎng)絡(luò)堆棧分層很清晰,每一層都各司其職,但和直接略過內(nèi)核態(tài)直接運行在用戶態(tài)(User Space)的Packet I/O相比,處理性能不是在一個檔次上,比如Netmap 、DPDK等。
MQTT-SN
針對沒有TCP/IP等網(wǎng)絡(luò)堆棧支持的終端環(huán)境,MQTT愛莫能助了。
在一些類似于傳感器電子元件中,資源十分受限,計算能力不足,嵌入TCP/IP網(wǎng)絡(luò)堆棧不現(xiàn)實,比較好的方式基于IEEE 802.15.4用于低速無線個人域網(wǎng)(LR-WPAN)的物理層和媒體接入控制層規(guī)范之上發(fā)送UDP數(shù)據(jù)包,每一個數(shù)據(jù)包最大128個字節(jié)。
MQTT-SN(MQTT For Sensor Networks)協(xié)議就是為了非常受限類似傳感器而設(shè)的,協(xié)議流程架構(gòu)比較有趣:
更多協(xié)議細節(jié),有待進一步閱讀。
TCP不是最適合的移動網(wǎng)絡(luò)傳輸協(xié)議
先來算一下網(wǎng)絡(luò)傳輸?shù)淖止?jié)數(shù)。
以太網(wǎng)幀頭至少18個字節(jié),IP頭固定20個字節(jié),TCP頭20個字節(jié)(UDP頭部8個字節(jié)),再加上電信寬帶計費的PPPoE的8個字節(jié):
- TCP數(shù)據(jù)包頭部信息至少占有66個字節(jié)
- UDP數(shù)據(jù)報頭部信息至少占有54個字節(jié)
UDP可以比TCP節(jié)省12個字節(jié)。
MQTT-SN協(xié)議選擇使用UDP,可以看出其在節(jié)省資源方面的努力。
再看看弱網(wǎng)環(huán)境。
- 在網(wǎng)絡(luò)可達情況下,UDP可以在TCP建立第一次握手期間就已經(jīng)把數(shù)據(jù)送達目的地
- 完成三次握手期間,UDP客戶端和UDP服務(wù)器在數(shù)據(jù)層面可以完成一次完整的交互(PING-PONG)
在網(wǎng)絡(luò)不好的情況下,UDP的時效性會好于TCP,TCP長連接中間交換過多、使之建立完整交互的過程成功率就很低。此種情況UDP的低延遲和實時性呈現(xiàn)的結(jié)果會表現(xiàn)的很突出。
TCP或HTTP理論上是可靠連接,但是在網(wǎng)絡(luò)不好的時候,也不是那么可靠。客戶端一般提交HTTP請求之后,沒有確認是否提交成功,在弱網(wǎng)環(huán)境下會產(chǎn)生丟包,服務(wù)器端嘛收不到。另TCP網(wǎng)絡(luò)堆棧會存在數(shù)據(jù)包重發(fā)機制 + 應(yīng)用層重發(fā)請求,可能會導(dǎo)致內(nèi)核處理多次數(shù)據(jù)包的重發(fā)(還有擁塞窗口會收縮,發(fā)包速度減慢),可能會加重弱網(wǎng)絡(luò)的負載。
和TCP相比,UDP的無連接,代表了它快速,資源消耗小,突出表現(xiàn)就是延遲較小。至于數(shù)據(jù)包丟失沒有重傳,上層的業(yè)務(wù)層面應(yīng)用協(xié)議/機制可以確保丟失的數(shù)據(jù)包重發(fā)或補發(fā)等,并且會更透明,安全的控性權(quán)。而TCP的包重發(fā),上層應(yīng)用沒有控制權(quán)限。
連接協(xié)議方面:
- TCP面向連接會產(chǎn)生狀態(tài)管理和維護,成本不小,比如經(jīng)常看到的客戶端reset異常等。一次完整的請求周期必須固定在一臺服務(wù)器上
- UDP無連接的特性。每次請求的數(shù)據(jù)包可以隨機分配到不同的機器上進行處理,可以做到完全無狀態(tài)化橫向擴展
總之,要實時性特診,或者快速抵達終端的特性,不妨考慮一下UDP。不過呢,很多時候UDP和TCP大家會混合著使用,會互相彌補其不足。
小結(jié)
若MQTT協(xié)議不能夠滿足業(yè)務(wù)需求,或許可考慮選擇定制,或簡化流程,或使用UDP重新實現(xiàn),或者使用TCP/HTTP作為補充等,不一而足。
想想,還真有點小激動呢! ------ 《萬萬沒想到》王大錘
posted on 2014-12-12 10:19 nieyong 閱讀(31299) 評論(17) 編輯 收藏 所屬分類: MQTT