新手也能看懂,訊息佇列其實很簡單

SnailClimb發表於2019-03-04

該文已加入開源專案:JavaGuide(一份涵蓋大部分Java程式設計師所需要掌握的核心知識的文件類專案,Star 數接近 16k)。地址: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資源哦!

我的公眾號

相關文章