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