MQTT特點
MQTT協議是為大量計算能力有限,且工作在低帶寬、不可靠的網絡的遠程傳感器和控制設備通訊而設計的協議。
1、使用發布/訂閱消息模式,提供一對多的消息發布,解除應用程序耦合
2、對負載內容屏蔽的消息傳輸。
3、使用TCP/IP提供網絡連接:
主流的MQTT是基於TCP連接進行數據推送的,但是同樣有基於UDP的版本,叫做MQTT-SN。這兩種版本由於基於不同的連接方式,優缺點自然也就各有不同了
4、有三種消息發布服務質量:
[1] “至多一次”,消息發布完全依賴底層TCP/IP網絡。會發生消息丟失或重復:
這一級別可用於如下情況,環境傳感器數據,丟失一次讀記錄無所謂,因為不久后還會有第二次發送。這一種方式主要普通APP的推送,倘若你的智能設備在消息推送時未聯網,推送過去沒收到,再次聯網也就收不到了。
[2] “至少一次”,確保消息到達,但消息重復可能會發生:
這一種方式比較雞肋,在我的想象中沒能想到這種質量的發送在常規的APP開發中有什么用處。
[3] “只有一次”,確保消息到達一次:
這一級別可用於如下情況,在計費系統中,消息重復或丟失會導致不正確的結果。這種最高質量的消息發布服務還可以用於即時通訊類的APP的推送,確保用戶收到且只會收到一次。
6、使用Last Will和Testament特性通知有關各方客戶端異常中斷的機制:
Last Will:即遺言機制,用於通知同一主題下的其他設備發送遺言的設備已經斷開了連接。
Testament:遺囑機制,功能類似於Last Will 。
由於MQTT 協議具有開放、簡單、輕量、易於實
現等優點, 因此他特別適用於低帶寬, 網絡不穩定,
網絡代價昂貴以及處理器和存儲器資源有限的嵌入式
設備和移動終端上.
術語:
客戶端(Client):
使用MQTT的程序或設備。客戶端總是通過網絡連接到服務端。它可以
- 發布應用消息給其它相關的客戶端。.
- 訂閱以請求接受相關的應用消息
- 取消訂閱以移除接受應用消息的請求。
- 從服務端斷開連接。
服務端(Server):
一個程序或設備,作為發送消息的客戶端和請求訂閱的客戶端之間的中介。服務端
- 接受來自客戶端的網絡連接
- 接受客戶端發布的應用消息
- 處理客戶端的訂閱和取消訂閱請求
- 轉發應用消息給符合條件的客戶端訂閱
訂閱(Subscription):
訂閱包含一個主題過濾器(Topic Filter)和一個最大的服務質量(QoS)等級。訂閱與單個會話(Session)關聯。會話可以包含多於一個的訂閱。會話的每個訂閱都有一個不同的主題過濾器。
主題名(Topic Name):
附加在應用消息上的一個標簽,服務端已知且與訂閱匹配。服務端發送應用消息的一個副本給每一個匹配的客戶端訂閱。
主題過濾器(Topic Filter:):
訂閱中包含的一個表達式,用於表示相關的一個或多個主題。主題過濾器可以使用通配符。
會話(Session):
客戶端和服務端之間的狀態交互。一些會話持續時長與網絡連接一樣,另一些可以在客戶端和服務端的多個連續網絡連接間擴展。
控制報文(MQTT Control Packet):
通過網絡連接發送的信息數據包。MQTT規范定義了十四種不同類型的控制報文,其中一個(PUBLISH報文)用於傳輸應用消息。
系統框架設計
整個服務器部分主要分成三個層次. 第一層是
MQTT 消息推送broker, 負責完成協議底層的網絡通
信機制以及針對各種不同類型消息的收發機制; 第二
層由身份驗證模塊、ACL 控制模塊、自動訂閱模塊、
話題統計模塊以及狀態監控模塊組成, 是在底層通信
機制的基礎上完善整個系統實際應用中所需的各項功
能; 第三層是數據存儲層, 為第二層的各個模塊提供
數據的支持, 用於各項數據的統計與交互. 整個系統
框架如圖1 所示.
各模塊的設計與實現
消息推送中間件
目前在各種平台上對於MQTT協議有許多不同的
實現, 這里所選擇的Mosquitto 是一款開源的基於C實
現的MQTT server/broker, 比較完整的實現了MQTT
協議中要求的各項基本功能, 可以在Windows, Linux
以及其它類Unix 系統中編譯運行.
數據存儲層
在數據存儲層中, 有的數據是需要經常讀取的,
比如用戶的名稱、密碼、ID 以及用戶間的好友關系等;
有的數據是需要經常寫入和修改的, 比如某些話題的
訂閱數; 有的數據是不經常讀取或寫入的, 比如服務
器的運行狀態. 為了提高效率, 對於需要經常讀取或
寫入的與用戶相關的數據, 用Redis 數據庫存儲, 對於
不經常讀取或寫入的與服務器狀態相關的數據, 用
MYSQL 數據庫存儲.
這里的Redis 是一個基於key-value 的開源no-sql
數據庫, 它將數據緩存在內存中, 相對於傳統的關系
型數據庫來說, 性能上有很大提高, 特別適用於對訪
問速度和並發性要求比較高的情況[6,7]. 同時Redis 也
支持數據的持久化, 並且支持list, set, hash 等多種不同
的數據結構. 在這里采用Redis 來存儲每個用戶的用
戶名密碼等相關信息和與話題有關的數量統計, 以處理大並發量下的用戶訪問請求.
密碼驗證模塊
在客戶端向服務器發起連接請求的時候, 服務器
必須對其進行密碼驗證, 以決定是否接受該連接請求,
驗證過程如圖2 所示.
為了增加數據庫的安全性, 在用戶注冊時需要對其
密碼加密后再將其密文存入數據庫, 這樣即使有人利用
非法手段侵入數據庫, 也無法得到用戶的真實密碼. 在
這里采用的MD5 加密算法是一種散列加密算法[8], 任何
一個密碼經過MD5 的HASH 散列計算之后將會產生一
個128bit 的序列. 但是缺點是兩個相同的密碼會產生相
同的散列, 為了彌補這個不足, 需要引入SALT 技術[9],
在用戶注冊時生成一個隨機數據, 與用戶密碼一起進行
散列計算, 然后將得到的密文和SALT 值一起存入數據
庫中, 這樣就可以保證即使用戶的密碼相同, 只要隨機
的SALT 值不同, 其密文就不相同.
在用戶發起連接請求時, 將用戶提交的密碼明文
與數據庫中的SALT 值一起進行散列計算, 將所得密文
與數據庫中密文進行比對即可對用戶密碼進行驗證.
ACL 控制模塊
為了規范用戶行為, 需要對其話題的訂閱和發布
進行權限控制. ACL(Access Control List)又稱為訪問控
制列表, 是一種通過匹配關系對訪問權限進行控制,
以加強系統安全性的技術[10,11].
ACL 表采用的格式為:
user <username>
[read/write] <topic>
或者
pattern [read/write] <topic>
第一種格式為特定用戶的權限控制規則, 第二種
格式為所有用戶的權限控制規則, read 表示具有訂閱
的權限, write 表示具有發布的權限, topic 采用層級結
構組織, 檢查的時候從左至右各層依次進行匹配.
ACL 表賦予用戶應該具有的最小權限, 只要滿足表中
的一項, 即表示驗證通過.
為了滿足移動社交網絡中的應用需求, 需要引入
通配符來表示用戶之間的關系, 在這里, 用%u 表示用
戶自身的用戶名, %c 表示用戶自身的ID 號, %f 表示用
戶的單向關注的關系, %F 表示用戶互相關注的關系
(這里我們稱之為好友), +表示話題中單層的通配, #表
示話題中若干層的通配, 一個典型的ACL 表項為:
pattern read user/%F/presence/+
該規則表示該用戶對所有他的好友的presence 之
下的一級子話題具有訂閱的權限.
自動訂閱模塊
在系統的實際應用中, 用戶需要接收各種各樣的
推送話題, 例如系統的廣播通知、好友的上線提醒、好
友發送的即時消息等, 如果每次上線的時候都由客戶
端來對這些話題進行訂閱, 不但會影響客戶端的性能,
還會占用移動終端的網絡資源, 尤其是在用戶關系復
雜, 需要訂閱大量話題的時候. 因此, 服務器可以在用
戶登錄成功之后為用戶自動訂閱這些話題, 以減少網
絡上的數據交互. 自動訂閱模塊流程如圖3 所示.
自動訂閱支持ACL 控制模塊中提到的各項通配
符, 並且將%u 替換為自身用戶名, %c 替換為自身ID,
含有%f 和%F 的表項會被通配為多個話題分別進行自
動訂閱. 一個典型的自動訂閱表項為:
user/%F/presence/+ 1
表示每個用戶上線時都會自動訂閱其好友的
presence之下一級的話題, QoS設為1, 中間以空格分隔.
話題統計模塊
當大量用戶在一段時間內同時訂閱某一話題, 或
者向某一話題發布消息時, 意味着這個話題是當前用
戶所關注的熱點. 因此, 對話題的統計有助於分析用
戶群體的行為方式以及當前社會生活中的熱點時事,
對於發掘用戶的潛在需求有重要的意義.
為了提高消息推動服務器的性能, 話題的統計數
據記錄在redis 數據庫中, 以topic-count 的形式存儲,
當用戶在統計的話題上進行訂閱或者發布的時候, 服
務器調用Redis 的INCR 或DECR 命令對統計數據進
行更新.
狀態監控模塊
要確保服務器長時間穩定的運行, 需要定期對系
統運行的狀態進行監控. 一方面要監控服務器本身的
系統信息, 如CPU 占用率、內存占用率、磁盤剩余空
間大小、網絡流量等等, 這些數據在linux 環境下可以
通過解析/proc 下相關文件的內容來獲取; 另一方面需
要監控消息推送服務的運行狀況, 如活躍的用戶數、
用戶訂閱話題的總數等. Mosquitto 提供了對這些信息
進行統計的機制, 所得的數據將由服務器以話題的形
式定期進行發布, 監控模塊需要以客戶的身份登錄並
訂閱相應話題以獲取相關信息, 采集到的結果插入到
MYSQL 數據庫中, 以供服務器進行性能分析以預警.