應用消息隊列能夠對系統進行解耦,流量削峯,在分佈式系統設計中,消息隊列是重要的組件之一。數據庫
在開發中應用過ActiveMQ,kafka等mq,不過對消息隊列背後的實現原理關注很少,其實瞭解消息隊列背後的實現特別重要,編程
好比對一致性等實現的關注,能夠幫助咱們在開發中避免踩坑,規避問題的出現。這篇文章簡單探討下當設計和實現一個消息隊列時,咱們須要關心哪些地方。併發
一個傳統意義上的消息隊列,須要支持消息的發送,接受和消息暫存的功能。負載均衡
在實際應用中,對消息隊列的要求遠不止於此,在不一樣的業務場景中,須要消息隊列提供如順序消息,消息可靠性,消息持久化等需求。異步
從消息可否會被即時接受和處理的角度,能夠把消息傳遞的方式分爲兩種。分佈式
一種是即時消息通信,也就是說消息從發送者一端發出後當即就能夠達到接收者一端;oop
另外一種方式稱爲延遲消息通信,即消息從某一端發出後,首先進入一個容器進行臨時存儲,當達到某種條件後,再由這個容器發送給另外一端。性能
延遲消息通信的容器實現就是消息隊列。ui
消息隊列須要支持消息的發送,消息暫存,和消息的異步消費,spa
除了基本功能之外,消息隊列在某些特殊的場景還須要支持事務,消息重試等功能。
爲了實現消息隊列的基礎功能,即消息的傳輸,存儲和消費,
須要從如下幾個維度去進行設計:
消息既是信息的載體,消息發送者須要知道如何構造消息,消息接收者須要知道如何解析消息,它們須要按照一種統一的格式描述消息,這種統一的格式稱之爲消息協議。沒有格式的消息是沒有意義的。
傳統的通訊協議標準有XMPP和AMQP協議等,如今更多的消息隊列從性能的角度出發使用本身設計實現的通訊協議。
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)。
Kafka的Producer、Broker和Consumer之間採用的是一套自行設計的基於TCP層的協議。Kafka的這套協議徹底是爲了Kafka自身的業務需求而定製的,而非要實現一套相似於Protocol Buffer的通用協議。
消息隊列經常保存在鏈表結構中,擁有權限的進程能夠向消息隊列中寫入或讀取消息。
對於分佈式系統,消息存儲的選擇有如下幾種:
從速度上內存顯然是最快的,對於容許消息丟失,消息堆積能力要求不高的場景(例如日誌),內存會是比較好的選擇。關係型數據庫則是最簡單的實現可靠存儲的方案,很適合用在可靠性要求很高,最終一致性的場景(例如交易消息)。
對於不須要100%保證數據完整性的場景,要求性能和消息堆積的場景,hbase也是一個很好的選擇,典型的好比 kafka的消息落地可使用hadoop。
消息隊列須要支持點對點和發佈/訂閱模式的消費模型, 消費端的消費進度也須要記錄,典型的如消費端重連的處理,參考Kafka對每一個Consumer提供一個偏移量的支持。
另外消息隊列選擇Pull仍是Push模型進行實現也很是重要。在消費端,ActiveMQ使用PUSH模型,而Kafka使用PULL模型,二者各有利弊。對於PUSH,broker很難控制數據發送給不一樣消費者的速度,而PULL能夠由消費者本身控制,可是PULL模型可能形成消費者在沒有消息的狀況下盲等,這種狀況下能夠經過long polling機制緩解。對於幾乎每時每刻都有消息傳遞的流式系統,使用Pull模型更合適。
消息隊列中消息的有序性直接依賴與存儲的選擇,而且和存儲的分佈式部署以及消費端的併發狀況密切相關。
消息的有序可使用存儲的順序性來支持,好比Kafka,在一個partition上是一段連續的存儲,能夠保證這一段連續的消息有序。
使用Redis能夠實現一個簡單的消息隊列,保證生產端和消費端都是單線程的生產和消費,由於底層數據機構有序,就能夠實現消息的有序。
消息投遞的可靠性涉及到分佈式數據一致性的話題,好比如何保證不丟數據,消息的冪等此類的問題。
RabbitMQ的設計是,當從隊列當中取出一個消息的時候,RabbitMQ須要應用顯式地回饋說已經獲取到了該消息。若是一段時間內不回饋,RabbitMQ會將該消息從新分配給另一個綁定在該隊列上的消費者。另外一種狀況是消費者斷開鏈接,可是獲取到的消息沒有回饋,則RabbitMQ一樣從新分配。
投遞的可靠性須要消費端和生產端一些約定的規則進行約束,保證投遞的可靠性,確定會影響性能,須要一些額外的工做來記錄消息的狀態等。
消息確認機制能夠給消息一致性提供支持,包括髮送端的確認和消費端的確認,AMQP 協議自己使用的是事務機制進行消息確認,可是事務機制性能較差,而且容易發生阻塞。
Kafka應用的是ACK機制,RabbitMQ也設計了單獨的消息確認機制。
消息隊列支持不一樣的投遞語義,以Kafka爲例,提供三種不一樣的語義:
相似的有阿里巴巴的MQ中間件,發送普通消息有三種實現方式:可靠同步發送、可靠異步發送、單向(Oneway)發送。