MQTT協議內部分享
時間:2018-07-26
講解提綱
- 協議文檔的解讀
- 協議適用的場景
- 協議正確的使用方式
技術知識準備
需要理解計算機網絡分層模型的工作原理,因為MQTT是一個應用層的協議。
圖片來源:HiveMQ官網
https://www.hivemq.com/blog/mqtt-essentials-part-3-client-broker-connection-establishment
Q: 什么是網絡連接?
A: 網絡連接是傳輸層定義的概念,在傳輸層以下只存在網絡數據包的相互交換。
所謂連接,其實也不是在網絡上有一條真實存在的數據通道。只要通信雙方在一段時間內持續保持數據包交換,就可以視為雙方建立的連接并沒有斷開。
連接的建立是依托于TCP協議的三次握手,一旦連接已經建立完畢,通信雙方就可以復用這條虛擬通道進行數據交換。如果連接保持長時間工作一直沒有被中斷,那么這樣的TCP連接就俗稱為長連接。
MQTT協議介紹
1. 協議全稱
Message Queue Telemetry Transport
,中文直譯:消息隊列遙測傳輸協議。
2. 版本歷史
1999年,IBM和合作伙伴共同發明了MQTT協議。
2004年,MQTT.org開放了論壇,供大家廣泛參與。
2011年,IBM建立了Eclipse開源項目Paho,并貢獻了代碼。
2013年,OASIS MQTT技術規范委員會成立。
2014年,MQTT正式成為推薦的物聯網傳輸協議標準。
3. 適用場景
- 物聯網設備 - - - - 設計初衷
在MQTT協議被設計出來的年代,還沒有物聯網這么時髦的詞匯,當年叫做遙測設備。
- 移動互聯網 - - - - 時代驅動
MQTT協議真正開始聲名鵲起的原因,是其正好恰恰踩中移動互聯網發展的節拍,為消息推送場景提供了一個既簡便又具有良好擴展性的現成解決方案。
4. 協議初步解讀
(1) 看命名
Message Queue -- 主要目的
提供消息的投遞服務
保障消息投遞的可靠性
Telemetry Transport -- 應用場景
低帶寬
網絡不穩定
設備性能不足
非常契合移動互聯網發展初期的網絡與設備實際工作場景。
(2) 看官方文檔摘要
http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html
(3) 看消息頭定義
Fixed Header
Header Structure
圖片來源:
來自: http://www.steves-internet-guide.com/mqtt-protocol-messages-overview/
解讀
可以看出,MQTT對消息頭的規定十分精簡,固定頭部占用空間大小僅為1個字節,一個最小的報文占用的空間也只有兩個字節(帶一字節的長度標識位)。
這也是MQTT協議針對不穩定及帶寬低下的網絡環境做出的特定設計 - - - - 盡可能地節省一切不必要的網絡開銷。
5. 控制報文 -- Control Packet
- 可歸類為三組
功能點 | Control Packet |
---|---|
連接相關 | CONNECT, CONNACK, PINGREQ, PINGRESP, DISCONNECT |
消息發布相關 | PUBLISH, PUBACK, PUBREC, PUBCOMP |
消息訂閱相關 | SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK |
Q:為什么MQTT協議需要心跳報文(PINGREQ, PINGRESP)來維護連接狀態,只監控該TCP的連接狀態是否可以實現目的?
A: TCP數據傳輸默認的超時時間過長,不符合應用層上細粒度的要求。
TCP數據傳輸超時的情況可分成三種:服務端斷開
、客戶端斷開
、中間網絡斷開
。
在前兩種場景下,若斷開操作是一方主動發起的,即表示為TCP連接正常結束,雙方走四次揮手流程;若程序異常結束,則會觸發被動斷開事件,通信另一方也能立刻感知到本次連接所打開的Socket
出現中斷異常。
唯獨中間網絡的狀態是通信雙方不能掌握的。在Linux系統下,TCP的連接超時由內核參數來控制,如果通信中的一方沒有得到及時回復,默認會主動再嘗試6次。如果還沒有得到及時回應,那么其才會認定本次數據超時。
連帶首次發包與六次重試,Linux系統下這7次發包的超時時間分別為2的0次方至2的6次方,即1秒、2秒、4秒、8秒、16秒、32秒、64秒,一共127秒。MQTT協議認為如此長的超時時間對應用層而言粒度太大,因此其在應用層上還單獨設計屬于自身的心跳響應控制。常見的MQTT連接超時多被設定為60秒
。
擴展知識 - TCP的KeepAlive機制:http://hengyunabc.github.io/why-we-need-heartbeat/
注:KeepAlive機制與上文敘述的超時場景不同。
KeepAlive是TCP協議對空閑已久的信道所增加的額外檢測機制。
6. 協議的工作方式
(1) 通信方式
Client <==> Server - - - - 傳輸過程
Client <==> Server <==> Client - - - - 傳達模式
Server 的專有稱謂 - - - - Broker(代理人)
(2) 匹配方式
Topic主題,相當于投遞地址,以UNIX路徑方式命名
發布消息:需要指定消息Topic的具體路徑,如 /topic/A/weather
-
訂閱消息:兩種方式
指定路徑訂閱 - - /topic/A/weather
模式匹配訂閱 - - /topic/+/weather, /topic/#
7. Qos保障設計
由通信中的報文標識符( Packet Identifier
)傳達。
-
Qos = 0
: At most once delivery- 一次數據交換:
Publish
- 一次數據交換:
-
Qos = 1
: At least once delivery- 兩次數據交換:
Publish, Pubrec
- 兩次數據交換:
-
Qos = 2
: Exactly once delivery- 四次數據交換:
Publish, Pubres, Pubrel, Pubcomp
- 四次數據交換:
Q:僅Publish與Pubrec能保證消息只被投遞一次嗎?
A:業務上可以實現,但MQTT協議并沒有如此設計,原因如下:
每個消息都會擁有屬于自己的報文標識符,但如果需要兩次數據交換就實現消息僅只收到一次,就需要通信雙方記錄下每次使用的報文標識符,并且在處理每一條消息時都需要去重處理,以防消息被重復消費。
但MQTT協議最初被設計的工作對象是輕量級物聯設備,為此在協議的設計中報文標識符被約定為可重用,以減少對設備性能的消耗,換回的代價不得不使用四次網絡數據交換,才能確保消息正好被消費一次。
額外參考資料:https://stackoverflow.com/questions/41329267/mqtt-qos2-why-use-4-packets
Q:兩個不同客戶端在發布與訂閱同一Topic下的消息時,都可以提出通信Qos要求,此時以哪項為基準?
A:偽命題,故意在分享時埋下坑,等人來踩。
兩個不同客戶端的通信是需要Broker
進行中轉,而不是直連。因此,通信中存在兩個不同的會話,雙方的Qos要求僅僅作用于它們與Broker
之間的會話,最終的Qos基準只會向最低要求方看齊。
8. 特殊消息
(1) 遺囑消息 - - - - Last Will Message
綁定至Topic上,每個Topic最多綁定一條遺囑消息
客戶端斷線后,服務器會將遺囑消息發布在此Topic上
(2) 保留消息 - - - - Retain Message
綁定至Topic上,每個Topic最多綁定一條保留消息
客戶端訂閱此Topic后,將立刻收到此條消息
一個遺囑消息也可以同樣是保留消息,兩者不沖突
(3) 工作方式
- 僅對消息的發布時機與發布方式制定規范,使用權移交至業務方
例:遺囑消息的正確使用方式可參考此篇文章:https://www.hivemq.com/blog/mqtt-essentials-part-9-last-will-and-testament
- 消息直接轉發,并不持久化存儲
雖然可以借助Retain Message實現綁定一條消息至某個Topic,以達到消息的暫時保留目的。
但首先Retain Message并不是為存儲場景而設計的,再次MQTT協議并沒有對消息的持久化作出規定,也就是說Broker重啟后,現有保留消息也將丟失。
Q:兩種特殊消息的使用場景?
A:遺囑消息,多用于客戶端間獲取互相之間異常斷線的消息通知;
保留消息,可保存最近一條廣播通知,多用于公告欄信息的發布。
MQTT應用實踐
1. 開源Broker
項目使用
Eclipse Mosquitto
:MQTT協議的最小集實現
所謂最小集實現,是指僅僅實現協議所規定的基本功能。
協議規定之外的,比如消息的持久化存儲、設備的連接管理等實際投產所需要的重要功能點,都需要業務方自行設計。
其他
有EMQ
, HiveMQ
, RabbitMQ MQTT Adapter
等。
2. 協議擴展
- SSL/TLS擴展:傳輸層安全保障
需要在客戶端與服務端中手動配置證書,較繁瑣。
- MQTT Over Websockets:基于
WebSockets
協議的實現版本,可供瀏覽器使用,可直接復用HTTPS網站證書
原因:瀏覽器上不能直接使用原生TCP協議。
好處:可直接復用網站證書,節省工作量。
- UDP版本:MQTT for Sensor Networks – MQTT-SN協議
注意:MQTT-SN與MQTT并不是一個協議,在設計上有一些不同。
官方文檔表示MQTT-SN被設計的場景是傳感器網絡,因為其網絡質量不足以支撐起一對穩定的TCP連接。
3. 業務系統的注意點
(1) Qos=2投遞保障
Qos=2
消息保障的網絡I/O次數過多,如果不是必需,盡少在程序里使用此類消息。
畢竟當初其設計的目的是為了減少設備的性能占用,但若應用場景并不是物聯網,而是用于手機、電腦或瀏覽器端等現在已不缺性能的設備上,最好在報文體中,使用UUID生成全局唯一的消息ID,然后自行在業務解析中判斷此報文是否被消費過。
或者,業務方在處理消息時保證其被消費的冪等性,也可消除重復消息對系統帶來的影響。
(2) 業務端的邏輯心跳
正如MQTT協議并沒有依賴TCP連接狀態,自己在應用層協議上實現心跳報文來控制連接狀態,業務方作為MQTT協議的使用者,也不要完全依賴協議的工作狀態,而是依托MQTT協議建立屬于業務本身的信息匯報機制,以加強系統的穩健性。
例1:MQTT連接超時默認設為60秒,對于多數互聯網應用而言,其直接面對終端用戶,這么長的時延顯然值得商榷。
例2:業務中不同的終端可能均屬于同一個用戶ID,那么僅僅依托MQTT協議的遺囑報文,不足以判斷用戶是否在線。
(3) Retain Message
Retain Message
可視為客戶端主動拉取的行為。如果業務系統采用HTTP+MQTT雙協議描述業務過程,主動拉取的操作也可使用HTTP請求替代。
(4) 機器資源的限制
作為一個長連接型的應用,上線前需要根據業務量級,評估對操作系統端口數與文件描述符的占用要求,以防服務器資源被打滿。
4. Mosquitto實踐
(1) 超時默認配置
連接異常斷開:60秒
心跳發送間隔:20秒,心跳回應超時:10秒
Pingreq由客戶端發起,服務器回應Pingresp。
客戶端在
空閑
時會不斷向服務端發起心跳狀態維護請求。其發起一個Pingreq報文后,默認10秒沒有收到回應后,會立刻發起新的Pingreq報文;如果有回應,20秒后再次重發請求。服務端若60秒內沒有收到客戶端的Pingreq報文,或者沒有收到來自此客戶端的發布消息,就視為此客戶端已斷開連接。
(2) 消息緩存的限制
在服務端的配置文件和客戶端的連接參數中,都擁有max_inflight_messages
此項配置,來維護Qos=1 or 2
消息是否被成功消費的狀態。
MQTT
最初被設計為物聯網級的通信協議,因此此參數的默認配額較小(大多數情況下被限制到10至20)。
但如果將MQTT協議應用至手機、PC或Web端的推送場景時,硬件性能已不在是瓶頸,在實際使用中推薦把此參數調大。
Broker
中的此配置可調為-1
,表示無限制;運行在服務器上的
MQTT-Client-as-Service
,設置至5000
以上為佳;用戶操作端上可相對保守一些,因為其不需要消費大量消息,可設為
50-100
左右。
(3) 高可用性保障
Mosquitto提供Bridge功能,需要我們自己配置。
Bridge
意為橋接,當我們把兩臺Broker橋接在一起時,只需要修改一臺Broker的配置,填上另一臺Broker的運行地址。前一臺Broker將作為客戶端發布與訂閱后一臺Broker的所有Topic,實現消息互通的目的。
兩臺Broker橋接之后,它們均可以作為接入節點提供服務,對外可配置一個虛擬IP或域名來訪問,與MySQL主備同步的讀寫分離方案有所不同。
橋接帶來的問題有以下幾點:
a) 為了保障服務的可用性,通常將兩臺服務器部署于不同的機房中。兩個機房之間的訪問時延需要盡可能低,否則消息的收發將會異常緩慢。
b) 增加一臺Bridge后網絡流量會翻倍,增加兩臺流量就翻兩倍,因此會加大對網絡的承載能力要求。
c) 在配置Bridge時,需要注意不能配置成消息環路,否則消息將會無限循環。
d) 兩個機房的網絡互聯出現故障時,消息的同步將會出問題,影響業務的可用性。
我的建議:
a) 在配置Bridge時,可以指定消息同步的Topic,而不是同步所有來規避流量放大的問題。
不過更推薦通過垂直切分的方案進行分流。作為消息隊列,只需要傳遞索引信息即可,文件傳輸不要走消息隊列。b) 若業務上不方便實現垂直切分的方案,可根據業務特點開發長連接代理,從而實現消息的定點投放。
因為無論是橋接轉發還是消息同步,面臨的問題都是每臺接入服務器都將接收全量消息,在大流量的背景下網卡速率會成瓶頸,解決方案還是采取水平切分的思想,避免消息流量的全面廣播。c) 使用兩臺Broker配置Bridge時,為了避免機房間網絡故障而造成的業務問題,最好引入可信的第三方存儲組件(如數據庫、ZooKeeper、Consul等),實現對監控信息的保存。若Brokers之間網絡出現互聯故障,推薦通過搶主方法,關停一臺以臨時解決業務上消息不同步的問題。
總結 與 QA
1. 請問MQTT 與 Websockets 之間的區別?
- Websockets
Websockets協議被設計的目的是為瀏覽器提供一個全雙工的通信協議,方便實現消息推送功能。
在Websockets協議被設計出來前,受限于HTTP協議的一問一答模型,消息的推送只能靠輪詢來實現,在資源消耗與時效性保障上,均難以達到令人滿意的效果。
Websockets協議復用了HTTP協議的頭部信息,告知瀏覽器接下來的操作將觸發協議升級,然后通信雙方繼續復用HTTP的Header,但報文內容已轉變為雙方均接受的新協議的格式。
Websockets協議改進了網頁瀏覽中的消息推送的方式,因此被廣泛應用在聊天、支付通知等實時性要求比較高的場合下。
- MQTT Over Websockets
MQTT協議重點在于消息隊列的實現,其對消息投遞的方式作出約定,并提供一些額外的通信保障。
MQTT可采取原生的TCP實現,也有基于Websockets的實現版本。當然后者在網絡字節的利用率上,不如前者那么精簡。但瀏覽器端無法直接使用TCP協議,所以就只能基于Websockets協議開發。
不過基于Websockets的應用也有方便之處:一是證書不需要額外配置,直接與網站共用一套基礎設施;二是可使用Nginx等工具管理流量,與普通HTTP流量可共用一套配置方法。
2. 本次分享的總結
(1) 學會理解網絡協議
MQTT非常適合入門,原因如下:
a) 簡單:相比較著名的HTTP協議,或者消息隊列中常用的AMQP協議,MQTT協議被設計得足夠簡單,因此脈絡的整理也相應方便很多。
b) 場景固定:MQTT協議被設計用來解決低寬帶高延遲下的消息投遞問題,目標明確。而像HTTP協議,互聯網上大部分內容都需要通過此訪問,面對的問題遠比MQTT復雜,因此理解起來不是那么直接。
c) API簡單:與上面兩點相輔相成,方便上手實踐。
(2) 學會理解系統設計
實際的應用場景遠比理想中的復雜,無法一招走遍天下,必須做好取舍。
MQTT協議在這方面做得很優秀,以后工作中可以作為參考,設計好自己負責的業務系統。