新手也能看懂,消息隊列其實很簡單

該文已加入開源項目:JavaGuide(一份涵蓋大部分Java程序員所需要掌握的核心知識的文檔類項目,Star 數接近 16k)。地址:https://github.com/Snailclimb...

本文內容思維導圖:
消息隊列總結

消息隊列其實很簡單

  “RabbitMQ?”“Kafka?”“RocketMQ?”...在日常學習與開發過程中,我們常常聽到消息隊列這個關鍵詞。我也在我的多篇文章中提到了這個概念。可能你是熟練使用消息隊列的老手,又或者你是不懂消息隊列的新手,不論你了不瞭解消息隊列,本文都將帶你搞懂消息隊列的一些基本理論。如果你是老手,你可能從本文學到你之前不曾注意的一些關於消息隊列的重要概念,如果你是新手,相信本文將是你打開消息隊列大門的一板磚。

一 什麼是消息隊列

  我們可以把消息隊列比作是一個存放消息的容器,當我們需要使用消息的時候可以取出消息供自己使用。消息隊列是分佈式系統中重要的組件,使用消息隊列主要是爲了通過異步處理提高系統性能和削峯、降低系統耦合性。目前使用較多的消息隊列有ActiveMQ,RabbitMQ,Kafka,RocketMQ,我們後面會一一對比這些消息隊列。

  另外,我們知道隊列 Queue 是一種先進先出的數據結構,所以消費消息時也是按照順序來消費的。比如生產者發送消息1,2,3...對於消費者就會按照1,2,3...的順序來消費。但是偶爾也會出現消息被消費的順序不對的情況,比如某個消息消費失敗又或者一個 queue 多個consumer 也會導致消息被消費的順序不對,我們一定要保證消息被消費的順序正確。

  除了上面說的消息消費順序的問題,使用消息隊列,我們還要考慮如何保證消息不被重複消費?如何保證消息的可靠性傳輸(如何處理消息丟失的問題)?......等等問題。所以說使用消息隊列也不是十全十美的,使用它也會讓系統可用性降低、複雜度提高,另外需要我們保障一致性等問題。

二 爲什麼要用消息隊列

  我覺得使用消息隊列主要有兩點好處:1.通過異步處理提高系統性能(削峯、減少響應所需時間);2.降低系統耦合性。如果在面試的時候你被面試官問到這個問題的話,一般情況是你在你的簡歷上涉及到消息隊列這方面的內容,這個時候推薦你結合你自己的項目來回答。

  《大型網站技術架構》第四章和第七章均有提到消息隊列對應用性能及擴展性的提升。

(1) 通過異步處理提高系統性能(削峯、減少響應所需時間)

通過異步處理提高系統性能
  如上圖,在不使用消息隊列服務器的時候,用戶的請求數據直接寫入數據庫,在高併發的情況下數據庫壓力劇增,使得響應速度變慢。但是在使用消息隊列之後,用戶的請求數據發送給消息隊列之後立即 返回,再由消息隊列的消費者進程從消息隊列中獲取數據,異步寫入數據庫。由於消息隊列服務器處理速度快於數據庫(消息隊列也比數據庫有更好的伸縮性),因此響應速度得到大幅改善。

  通過以上分析我們可以得出消息隊列具有很好的削峯作用的功能——即通過異步處理,將短時間高併發產生的事務消息存儲在消息隊列中,從而削平高峯期的併發事務。 舉例:在電子商務一些秒殺、促銷活動中,合理使用消息隊列可以有效抵禦促銷活動剛開始大量訂單涌入對系統的衝擊。如下圖所示:
合理使用消息隊列可以有效抵禦促銷活動剛開始大量訂單涌入對系統的衝擊

  因爲用戶請求數據寫入消息隊列之後就立即返回給用戶了,但是請求數據在後續的業務校驗、寫數據庫等操作中可能失敗。因此使用消息隊列進行異步處理之後,需要適當修改業務流程進行配合,比如用戶在提交訂單之後,訂單數據寫入消息隊列,不能立即返回用戶訂單提交成功,需要在消息隊列的訂單消費者進程真正處理完該訂單之後,甚至出庫後,再通過電子郵件或短信通知用戶訂單成功,以免交易糾紛。這就類似我們平時手機訂火車票和電影票。

(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)模型

點到點(P2P)模型
  使用隊列(Queue)作爲消息通信載體;滿足生產者與消費者模式,一條消息只能被一個消費者使用,未被消費的消息在隊列中保留直到被消費或超時。比如:我們生產者發送100條消息的話,兩個消費者來消費一般情況下兩個消費者會按照消息發送的順序各自消費一半(也就是你一個我一個的消費。)

② 發佈/訂閱(Pub/Sub)模型

發佈/訂閱(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 唯一的一點劣勢是有可能消息重複消費,那麼對數據準確性會造成極其輕微的影響,在大數據領域中以及日誌採集中,這點輕微影響可以忽略這個特性天然適合大數據實時計算以及日誌收集。

參考:《Java工程師面試突擊第1季-中華石杉老師》

ThoughtWorks准入職Java工程師。專注Java知識分享!開源 Java 學習指南——JavaGuide(15k+ Star)的作者。公衆號多篇文章被各大技術社區轉載。公衆號後臺回覆關鍵字“1”可以領取一份我精選的Java資源哦!

我的公衆號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章