一、什麼是消息隊列 我們可以把消息隊列比作是一個存放消息的容器,當我們需要使用消息的時候可以取出消息供自己使用。消息隊列是分散式系統中重要的組件,使用消息隊列主要是為了通過非同步處理提高系統性能和削峰、降低系統耦合性。目前使用較多的消息隊列有ActiveMQ,RabbitMQ,Kafka,Rocket ...
一、什麼是消息隊列
我們可以把消息隊列比作是一個存放消息的容器,當我們需要使用消息的時候可以取出消息供自己使用。消息隊列是分散式系統中重要的組件,使用消息隊列主要是為了通過非同步處理提高系統性能和削峰、降低系統耦合性。目前使用較多的消息隊列有ActiveMQ,RabbitMQ,Kafka,RocketMQ,我們後面會一一對比這些消息隊列。
另外,我們知道隊列 Queue 是一種先進先出的數據結構,所以消費消息時也是按照順序來消費的。比如生產者發送消息1,2,3...對於消費者就會按照1,2,3...的順序來消費。但是偶爾也會出現消息被消費的順序不對的情況,比如某個消息消費失敗又或者一個 queue 多個consumer 也會導致消息被消費的順序不對,我們一定要保證消息被消費的順序正確。
除了上面說的消息消費順序的問題,使用消息隊列,我們還要考慮如何保證消息不被重覆消費?如何保證消息的可靠性傳輸(如何處理消息丟失的問題)?......等等問題。所以說使用消息隊列也不是十全十美的,使用它也會讓系統可用性降低、複雜度提高,另外需要我們保障一致性等問題。
二、為什麼要用消息隊列
我覺得使用消息隊列主要有兩點好處:1.通過非同步處理提高系統性能(削峰、減少響應所需時間);2.降低系統耦合性。如果在面試的時候你被面試官問到這個問題的話,一般情況是你在你的簡歷上涉及到消息隊列這方面的內容,這個時候推薦你結合你自己的項目來回答。
《大型網站技術架構》第四章和第七章均有提到消息隊列對應用性能及擴展性的提升。
2.1通過非同步處理提高系統性能(削峰、減少響應所需時間)
如上圖,在不使用消息隊列伺服器的時候,用戶的請求數據直接寫入資料庫,在高併發的情況下資料庫壓力劇增,使得響應速度變慢。但是在使用消息隊列之後,用戶的請求數據發送給消息隊列之後立即 返回,再由消息隊列的消費者進程從消息隊列中獲取數據,非同步寫入資料庫。由於消息隊列伺服器處理速度快於資料庫(消息隊列也比資料庫有更好的伸縮性),因此響應速度得到大幅改善。
通過以上分析我們可以得出消息隊列具有很好的削峰作用的功能——即通過非同步處理,將短時間高併發產生的事務消息存儲在消息隊列中,從而削平高峰期的併發事務。 舉例:在電子商務一些秒殺、促銷活動中,合理使用消息隊列可以有效抵禦促銷活動剛開始大量訂單涌入對系統的衝擊。如下圖所示:
因為用戶請求數據寫入消息隊列之後就立即返回給用戶了,但是請求數據在後續的業務校驗、寫資料庫等操作中可能失敗。因此使用消息隊列進行非同步處理之後,需要適當修改業務流程進行配合,比如用戶在提交訂單之後,訂單數據寫入消息隊列,不能立即返回用戶訂單提交成功,需要在消息隊列的訂單消費者進程真正處理完該訂單之後,甚至出庫後,再通過電子郵件或簡訊通知用戶訂單成功,以免交易糾紛。這就類似我們平時手機訂火車票和電影票。
2.2降低系統耦合性
我們知道如果模塊之間不存在直接調用,那麼新增模塊或者修改模塊就對其他模塊影響較小,這樣系統的可擴展性無疑更好一些。
我們最常見的事件驅動架構類似生產者消費者模式,在大型網站中通常用利用消息隊列實現事件驅動結構。如下圖所示:
消息隊列使利用發佈-訂閱模式工作,消息發送者(生產者)發佈消息,一個或多個消息接受者(消費者)訂閱消息。
從上圖可以看到消息發送者(生產者)和消息接受者(消費者)之間沒有直接耦合,消息發送者將消息發送至分散式消息隊列即結束對消息的處理,消息接受者從分散式消息隊列獲取該消息後進行後續處理,並不需要知道該消息從何而來。對新增業務,只要對該類消息感興趣,即可訂閱該消息,對原有系統和業務沒有任何影響,從而實現網站業務的可擴展性設計。
消息接受者對消息進行過濾、處理、包裝後,構造成一個新的消息類型,將消息繼續發送出去,等待其他消息接受者訂閱該消息。因此基於事件(消息對象)驅動的業務架構可以是一系列流程。
另外為了避免消息隊列伺服器宕機造成消息丟失,會將成功發送到消息隊列的消息存儲在消息生產者伺服器上,等消息真正被消費者伺服器處理後才刪除消息。在消息隊列伺服器宕機後,生產者伺服器會選擇分散式消息隊列伺服器集群中的其他伺服器發佈消息。
備註: 不要認為消息隊列只能利用發佈-訂閱模式工作,只不過在解耦這個特定業務環境下是使用發佈-訂閱模式的。除了發佈-訂閱模式,還有點對點訂閱模式(一個消息只有一個消費者),我們比較常用的是發佈-訂閱模式。 另外,這兩種消息模型是 JMS 提供的,AMQP 協議還提供了 5 種消息模型。
三、使用消息隊列帶來的一些問題
- 系統可用性降低: 系統可用性在某種程度上降低,為什麼這樣說呢?在加入MQ之前,你不用考慮消息丟失或者說MQ掛掉等等的情況,但是,引入MQ之後你就需要去考慮了!
- 系統複雜性提高: 加入MQ之後,你需要保證消息沒有被重覆消費、處理消息丟失的情況、保證消息傳遞的順序性等等問題!
- 一致性問題: 我上面講了消息隊列可以實現非同步,消息隊列帶來的非同步確實可以提高系統響應速度。但是,萬一消息的真正消費者並沒有正確消費消息怎麼辦?這樣就會導致數據不一致的情況了!
四、JMS VS AMQP
4.1 JMS
4.1.1 JMS 簡介
JMS(JAVA Message Service,java消息服務)是java的消息服務,JMS的客戶端之間可以通過JMS服務進行非同步的消息傳輸。JMS(JAVA Message Service,Java消息服務)API是一個消息服務的標準或者說是規範,允許應用程式組件基於JavaEE平臺創建、發送、接收和讀取消息。它使分散式通信耦合度更低,消息服務更加可靠以及非同步性。
ActiveMQ 就是基於 JMS 規範實現的。
4.1.2 JMS兩種消息模型
①點到點(P2P)模型
為消息通信載體;滿足生產者與消費者模式,一條消息只能被一個消費者使用,未被消費的消息在隊列中保留直到被消費或超時。比如:我們生產者發送100條消息的話,兩個消費者來消費一般情況下兩個消費者會按照消息發送的順序各自消費一半(也就是你一個我一個的消費。)
② 發佈/訂閱(Pub/Sub)模型
發佈訂閱模型(Pub/Sub) 使用主題(Topic)作為消息通信載體,類似於廣播模式;發佈者發佈一條消息,該消息通過主題傳遞給所有的訂閱者,在一條消息廣播之後才訂閱的用戶則是收不到該條消息的。
4.1.3 JMS 五種不同的消息正文格式
JMS定義了五種不同的消息正文格式,以及調用的消息類型,允許你發送並接收以一些不同形式的數據,提供現有消息格式的一些級別的相容性。
- StreamMessage -- Java原始值的數據流
- MapMessage--一套名稱-值對
- TextMessage--一個字元串對象
- ObjectMessage--一個序列化的 Java對象
- BytesMessage--一個位元組的數據流
4.2 AMQP
AMQP,即Advanced Message Queuing Protocol,一個提供統一消息服務的應用層標準 高級消息隊列協議(二進位應用層協議),是應用層協議的一個開放標準,為面向消息的中間件設計,相容 JMS。基於此協議的客戶端與消息中間件可傳遞消息,並不受客戶端/中間件同產品,不同的開發語言等條件的限制。
RabbitMQ 就是基於 AMQP 協議實現的。
4.3 JMS vs AMQP
對比方向 | JMS | AMQP |
---|---|---|
定義 | Java API | 協議 |
跨語言 | 否 | 是 |
跨平臺 | 否 | 是 |
支持消息類型 | 提供兩種消息模型:①Peer-2-Peer;②Pub/sub | 提供了五種消息模型:①direct exchange;②fanout exchange;③topic change;④headers exchange;⑤system exchange。本質來講,後四種和JMS的pub/sub模型沒有太大差別,僅是在路由機制上做了更詳細的劃分; |
支持消息類型 | 支持多種消息類型 ,我們在上面提到過 | byte[](二進位) |
總結:
- AMQP 為消息定義了線路層(wire-level protocol)的協議,而JMS所定義的是API規範。在 Java 體系中,多個client均可以通過JMS進行交互,不需要應用修改代碼,但是其對跨平臺的支持較差。而AMQP天然具有跨平臺、跨語言特性。
- JMS 支持TextMessage、MapMessage 等複雜的消息類型;而 AMQP 僅支持 byte[] 消息類型(複雜的類型可序列化後發送)。
- 由於Exchange 提供的路由演算法,AMQP可以提供多樣化的路由方式來傳遞消息到消息隊列,而 JMS 僅支持 隊列 和 主題/訂閱 方式兩種。
五、常見的消息隊列對比
對比方向 | 概要 |
---|---|
吞吐量 | 萬級的 ActiveMQ 和 RabbitMQ 的吞吐量(ActiveMQ 的性能最差)要比 十萬級甚至是百萬級的 RocketMQ 和 Kafka 低一個數量級。 |
可用性 | 都可以實現高可用。ActiveMQ 和 RabbitMQ 都是基於主從架構實現高可用性。RocketMQ 基於分散式架構。 kafka 也是分散式的,一個數據多個副本,少數機器宕機,不會丟失數據,不會導致不可用 |
時效性 | RabbitMQ 基於erlang開發,所以併發能力很強,性能極其好,延時很低,達到微秒級。其他三個都是 ms 級。 |
功能支持 | 除了 Kafka,其他三個功能都較為完備。 Kafka 功能較為簡單,主要支持簡單的MQ功能,在大數據領域的實時計算以及日誌採集被大規模使用,是事實上的標準 |
消息丟失 | ActiveMQ 和 RabbitMQ 丟失的可能性非常低, RocketMQ 和 Kafka 理論上不會丟失。 |
總結:
- ActiveMQ 的社區算是比較成熟,但是較目前來說,ActiveMQ 的性能比較差,而且版本迭代很慢,不推薦使用。
- RabbitMQ 在吞吐量方面雖然稍遜於 Kafka 和 RocketMQ ,但是由於它基於 erlang 開發,所以併發能力很強,性能極其好,延時很低,達到微秒級。但是也因為 RabbitMQ 基於 erlang 開發,所以國內很少有公司有實力做erlang源碼級別的研究和定製。如果業務場景對併發量要求不是太高(十萬級、百萬級),那這四種消息隊列中,RabbitMQ 一定是你的首選。如果是大數據領域的實時計算、日誌採集等場景,用 Kafka 是業內標準的,絕對沒問題,社區活躍度很高,絕對不會黃,何況幾乎是全世界這個領域的事實性規範。
- RocketMQ 阿裡出品,Java 系開源項目,源代碼我們可以直接閱讀,然後可以定製自己公司的MQ,並且 RocketMQ 有阿裡巴巴的實際業務場景的實戰考驗。RocketMQ 社區活躍度相對較為一般,不過也還可以,文檔相對來說簡單一些,然後介面這塊不是按照標準 JMS 規範走的有些系統要遷移需要修改大量代碼。還有就是阿裡出台的技術,你得做好這個技術萬一被拋棄,社區黃掉的風險,那如果你們公司有技術實力我覺得用RocketMQ 挺好的
- kafka 的特點其實很明顯,就是僅僅提供較少的核心功能,但是提供超高的吞吐量,ms 級的延遲,極高的可用性以及可靠性,而且分散式可以任意擴展。同時 kafka 最好是支撐較少的 topic 數量即可,保證其超高吞吐量。kafka 唯一的一點劣勢是有可能消息重覆消費,那麼對數據準確性會造成極其輕微的影響,在大數據領域中以及日誌採集中,這點輕微影響可以忽略這個特性天然適合大數據實時計算以及日誌收集。