百日筑基第十九天-一頭扎進消息隊列2
消息隊列的通訊協議
目前業界的通信協議可以分為公有協議和私有協議兩種。公有協議指公開的受到認可的具有規 范的協議,比如 JMS、HTTP、STOMP 等。私有協議是指根據自身的功能和需求設計的協 議,一般不具備通用性,比如 Kafka、RocketMQ、Puslar 的協議都是私有協議。
大多數消息隊列為了自身的功能支持、迭代速度、靈活性考慮,在核心通信協議的選擇上 不會選擇公有協議,都會選擇自定義私有協議。
從技術上來看,私有協議設計一般需要包含三個步驟。
- 網絡通信協議選型,指計算機七層網絡模型中的協議選擇。比如傳輸層的 TCP/UDP、應用 層的 HTTP/WebSocket 等。 從功能需求出發,為了保證性能和可靠性,幾乎所有主流消息隊列在核心生產、消費鏈路的協 議選擇上,都是基于可靠性高、長連接的 TCP 協議。
- 應用通信協議設計,指如何約定客戶端和服務端之間的通信規則。比如如何識別請求內容、 如何確定請求字段信息等。 從應用通信協議構成的角度,協議一般會包含**協議頭(數據源信息)和協議體(業務數據)**兩部分。
- 編解碼(序列化 / 反序列化)實現,用于將二進制的消息內容解析為程序可識別的數據格式(或反過來用于傳輸)。
消息隊列的網絡模塊
網絡模塊:對消息隊列來說,網絡模塊是核心組件之一,網絡模塊的性能很大程度上決定了消息傳輸的能力和整體性能。
消息隊列是需要滿足高吞吐、高可靠、低延時,并支持多語言訪問的基礎軟件,網絡模塊最需要解決的是性能、穩定性、開發成本三個問題。
從技術上來看,高性能網絡模塊的設計可以分為如何高效管理大量的 TCP 連接、如何快速處理高并發的請求、如何提高穩定性和降低開發成本等三個方面。
- 高效處理大量 TCP 連接:在消息隊列中主要有**單條 TCP 連接的復用(RabbitMQ)和IO多路復用(Kakfa、RocketMQ、Pulsar )**兩種技術思路。
- 快速處理高并發的請求:Reactor 模型是一種處理并發服務請求的事件設計模式,當主流程收到請求后,通過多路分離處理的方式,把請求分發給相應的請求處理器處理。
- 提高穩定性和降低開發成本:在消息隊列的網絡編程模型中,為了提高穩定性或者降低成本,選擇現成的、成熟的 NIO 框架是一個更好的方案(kafka如是)。
消息隊列的存儲模塊
存儲模塊作為消息隊列高吞吐、低延時、高可靠特性的基礎保證,可以說是最核心的模塊。
消息隊列中的數據一般分為元數據和消息數據。元數據是指 Topic、Group、User、ACL、Config 等集群維度的資源數據信息,消息數據指客戶端寫入的用戶的業務數據。
-
元數據信息的存儲:元數據信息的特點是數據量比較小,不會經常讀寫,但是需要保證數據的強一致和高可靠,不允許出現數據的丟失。基于第三方組件來實現元數據的存儲是目前業界的主流選擇。比如 Kafka ZooKeeper 版本、 Pulsar、RocketMQ 用的就是這個思路,其中 Kakfa 和 Pulsar 的元數據存儲在 ZooKeeper 中,RocketMQ 存儲在 NameServer 中。另一種思路,集群內部實現元數據的存儲是指在集群內部完成元數據的存儲和分發。也就是在 集群內部實現類似第三方組件一樣的元數據服務,比如基于 Raft 協議實現內部的元數據存儲 模塊或依賴一些內置的數據庫。目前 Kafka 去 ZooKeeper 的版本、RabbitMQ 的 Mnesia、 Kafka 的 C++ 版本 RedPanda 用的就是這個思路。
-
數據消息的存儲:第一個思路,每個分區對應一個文件的形式去存儲數據(kafka如是)。因為消息隊列在大部分情況下的讀寫是有序的,所以這種機制在讀寫性能上的表現是最高的。第二種思路,每個節點上所有分區的數據都存儲在同一個文件中,這種方案需要為每個分區維護一個對應的索引文件,索引文件里會記錄每條消息在 File 里面的位置信息,以便快速定位到具體的消息內容。(目前 RocketMQ、RabbitMQ 和 Pulsar 的底層存儲 BookKeeper 用的就是這個方案)
數據分段的規則一般是根據大小來進行的,比如默認 1G 一個文件,同時會支持配置項調整分段數據的大小。
從技術上來看,當數據段到達了規定的大小后,就會新創建一個新文件來保存數據。
如果進行了分段,消息數據可能分布在不同的文件中。所以我們在讀取數據的時候,就需要先 定位消息數據在哪個文件中。為了滿足這個需求,技術上一般有根據偏移量定位或根據索引定位兩種思路。
- 根據偏移量(Offset)來定位消息在哪個分段文件中,是指通過記錄每個數據段文件的起始偏 移量、中止偏移量、消息的偏移量信息,來快速定位消息在哪個文件。
- 如果用索引定位,會直接存儲消息對應的文件信息,而不是通過偏移量來定位到具體文件。(RabbitMQ 和 RocketMQ 如是)
消息數據存儲格式一般包含消息寫入文件的格式和消息內容的格式兩個方面。
- 消息寫入文件的格式指消息是以什么格式寫入到文件中的,比如 JSON 字符串或二進制。從 性能和空間冗余的角度來看,消息隊列中的數據基本都是以二進制的格式寫入到文件的。
- 消息內容的格式是指寫入到文件中的數據都包含哪些信息。對于一個成熟的消息隊列來說,消 息內容格式不僅關系功能維度的擴展,還牽涉性能維度的優化。(如Kafka 的消息內容包含了業務會感知到的消息的 Header、Key、Value,還包含了時間戳、偏移量、協議版本、數據長度和大小、校驗碼等基礎信息,最后還包含了壓縮、事 務、冪等 Kafka 業務相關的信息)
消息隊列中的數據最終都會刪除,時間周期短的話幾小時、甚至幾分鐘,正常情況一天、三 天、七天,長的話可能一個月,基本很少有場景需要在消息隊列中存儲一年的數據
消息隊列的數據過期機制一般有手動刪除和自動刪除兩種形式。從實現上看主要有三種思路: 消費完成執行 ACK 刪除、數據根據時間和保留大小刪除、 ACK 機制和過期機制相結合。