當設計消息隊列時我們關心什麼
應用消息隊列可以對係統進行解耦,流量削峰,在分布式係統設計中,消息隊列是重要的組件之一。
在開發中應用過ActiveMQ,kafka等mq,不過對消息隊列背後的實現原理關注不多,其實了解消息隊列背後的實現特別重要,
比如對一致性等實現的關注,可以幫助我們在開發中避免踩坑,規避問題的出現。這篇文章簡單探討下當設計和實現一個消息隊列時,我們需要關心哪些地方。
消息隊列功能和特性
一個傳統意義上的消息隊列,需要支持消息的發送,接受和消息暫存的功能。
在實際應用中,對消息隊列的要求遠不止於此,在不同的業務場景中,需要消息隊列提供如順序消息,消息可靠性,消息持久化等需求。
1.即時通信和消息隊列
從消息能否會被即時接受和處理的角度,可以把消息傳遞的方式分為兩種。
一種是即時消息通訊,也就是說消息從發送者一端發出後立即就可以達到接收者一端;
另一種方式稱為延遲消息通訊,即消息從某一端發出後,首先進入一個容器進行臨時存儲,當達到某種條件後,再由這個容器發送給另一端。
延遲消息通訊的容器實現就是消息隊列。
2.消息隊列基礎功能
消息隊列需要支持消息的發送,消息暫存,和消息的異步消費,
3.消息隊列需要支持的特性
除了基本功能以外,消息隊列在某些特殊的場景還需要支持事務,消息重試等功能。
- 消息的順序
- 投遞可靠性保證
- 消息持久化
- 支持不同消息模型
- 多實例集群功能
- 分布式環境下的負載均衡
消息隊列的基礎設計
為了實現消息隊列的基礎功能,即消息的傳輸,存儲和消費,
需要從以下幾個維度去進行設計:
- 通信協議
- 存儲選擇
- 消費關係維護
1.通信協議
消息既是信息的載體,消息發送者需要知道如何構造消息,消息接收者需要知道如何解析消息,它們需要按照一種統一的格式描述消息,這種統一的格式稱之為消息協議。沒有格式的消息是沒有意義的。
傳統的通信協議標準有XMPP和AMQP協議等,現在更多的消息隊列從性能的角度出發使用自己設計實現的通信協議。
(1)AMQP規範和JMS規範
AMQP 是 Advanced Message Queuing Protocol,即高級消息隊列協議。AMQP不是一個具體的消息隊列實現,而 是一個標準化的消息中間件協議。目標是讓不同語言,不同係統的應用互相通信,並提供一個簡單統一的模型和編程接口。 目前主流的ActiveMQ和RabbitMQ都支持AMQP協議。
JMS是Java平台的一部分,是一種應用於異步消息傳遞的標準API,JMS可以允許不同應用、不同模塊之間實現可靠、異步數據通信。
在JMS中,支持兩種消息模型,點對點(Point-to-point)和發布-訂閱(Publish and subscribe), 這兩種模式分別對應於JMS中的兩種消息目標(Message Destination):隊列及主題(queue/topic)。
(2)Kafka的通信協議
Kafka的Producer、Broker和Consumer之間采用的是一套自行設計的基於TCP層的協議。Kafka的這套協議完全是為了Kafka自身的業務需求而定製的,而非要實現一套類似於Protocol Buffer的通用協議。
2.消息存儲
消息隊列常常保存在鏈表結構中,擁有權限的進程可以向消息隊列中寫入或讀取消息。
對於分布式係統,消息存儲的選擇有以下幾種:
- 內存
- 本地文件係統
- 分布式文件係統
- 關係型數據庫
- NoSQL數據庫
從速度上內存顯然是最快的,對於允許消息丟失,消息堆積能力要求不高的場景(例如日誌),內存會是比較好的選擇。關係型數據庫則是最簡單的實現可靠存儲的方案,很適合用在可靠性要求很高,最終一致性的場景(例如交易消息)。
對於不需要100%保證數據完整性的場景,要求性能和消息堆積的場景,hbase也是一個很好的選擇,典型的比如 kafka的消息落地可以使用hadoop。
3.消費關係維護
消息隊列需要支持點對點和發布/訂閱模式的消費模型, 消費端的消費進度也需要記錄,典型的如消費端重連的處理,參考Kafka對每個Consumer提供一個偏移量的支持。
另外消息隊列選擇Pull還是Push模型進行實現也非常重要。在消費端,ActiveMQ使用PUSH模型,而Kafka使用PULL模型,兩者各有利弊。對於PUSH,broker很難控製數據發送給不同消費者的速度,而PULL可以由消費者自己控製,但是PULL模型可能造成消費者在沒有消息的情況下盲等,這種情況下可以通過long polling機製緩解。對於幾乎每時每刻都有消息傳遞的流式係統,使用Pull模型更合適。
消息隊列高級特性實現
1.消息有序支持
消息隊列中消息的有序性直接依賴與存儲的選擇,並且和存儲的分布式部署以及消費端的並發情況密切相關。
消息的有序可以使用存儲的順序性來支持,比如Kafka,在一個partition上是一段連續的存儲,可以保證這一段連續的消息有序。
使用Redis可以實現一個簡單的消息隊列,保證生產端和消費端都是單線程的生產和消費,因為底層數據機構有序,就可以實現消息的有序。
2.投遞可靠性支持
消息投遞的可靠性涉及到分布式數據一致性的話題,比如如何保證不丟數據,消息的冪等此類的問題。
RabbitMQ的設計是,當從隊列當中取出一個消息的時候,RabbitMQ需要應用顯式地回饋說已經獲取到了該消息。如果一段時間內不回饋,RabbitMQ會將該消息重新分配給另外一個綁定在該隊列上的消費者。另一種情況是消費者斷開連接,但是獲取到的消息沒有回饋,則RabbitMQ同樣重新分配。
投遞的可靠性需要消費端和生產端一些約定的規則進行約束,保證投遞的可靠性,肯定會影響性能,需要一些額外的工作來記錄消息的狀態等。
3.消息確認機製
消息確認機製可以給消息一致性提供支持,包括發送端的確認和消費端的確認,AMQP 協議本身使用的是事務機製進行消息確認,但是事務機製性能較差,並且容易發生阻塞。
Kafka應用的是ACK機製,RabbitMQ也設計了單獨的消息確認機製。
4.消息發送和投遞方式
消息隊列支持不同的投遞語義,以Kafka為例,提供三種不同的語義:
- At most once 消息可能會丟,但絕不會重複傳輸
- At least one 消息絕不會丟,但可能會重複傳輸
- Exactly once 每條消息肯定會被傳輸一次且僅傳輸一次
類似的有阿裏巴巴的MQ中間件,發送普通消息有三種實現方式:可靠同步發送、可靠異步發送、單向(Oneway)發送。
- 可靠同步發送:
- 可靠異步發送:
- 單向(Oneway)發送
最後更新:2017-04-07 21:25:10