大寫的服,看完這篇你還不懂RocketMQ算我輸

AlbenXie發表於2020-09-25

目錄

  1. RocketMQ介紹
  2. RocketMQ概念
  3. 為什麼要用RocketMQ?
    1. 非同步解耦
    2. 削峰填谷
    3. 分散式事務最終一致性
    4. 資料分發
  4. RocketMQ架構
  5. RocketMQ訊息型別
    1. 普通訊息
    2. 順序訊息
    3. 定時訊息
    4. 事務訊息
  6. 最佳實踐
    1. 訊息重試
    2. 訊息過濾
    3. 消費模式
    4. 消費冪等
  7. 本地事務訊息封裝
  8. 參考程式碼

RocketMQ 介紹

Apache RocketMQ 是一款 低延遲、高併發、高可用、高可靠的分散式訊息中介軟體。訊息佇列 RocketMQ 可為分散式應用系統提供非同步解耦和削峰填谷的能力,同時也具備網際網路應用所需的海量訊息堆積、高吞吐、可靠重試等特性。

RocketMQ 概念

  • Topic:訊息主題,用於將一類的訊息進行歸類,比如訂單主題,就是所有訂單相關的訊息都可以由這個主題去承載,生產者向這個主題傳送訊息。
  • 生產者:負責生產訊息併傳送訊息到 Topic 的角色。
  • 消費者:負責從 Topic 接收並消費訊息 的角色。
  • 訊息:生產者向 Topic 傳送的內容,會被消費者消費。
  • 訊息屬性:生產者傳送的時候可以為訊息自定義一些業務相關的屬性,比如 Message Key 和 Tag 等。
  • Group:一類生產者或消費者,這類生產者或消費者通常生產或消費同一類訊息,且訊息釋出或訂閱的邏輯一致。

為什麼要使用 RocketMQ?

非同步解耦

隨著微服務架構的流行,服務之間的關係梳理非常重要。非同步解耦可以降低服務之間的耦合程度,同時也能提高服務的吞吐量。

使用非同步解耦的業務場景非常多,因為每個行業的業務都會不太一樣,以一些比較通用的業務來說明相信大家都能理解。

比如電商行業的下單業務場景,以最簡單的下單流程來說,下單流程如下:

  1. 鎖庫存
  2. 建立訂單
  3. 使用者支付
  4. 扣減庫存
  5. 給使用者傳送購買簡訊通知
  6. 給使用者增加積分
  7. 通知商家發貨

我們以下單成功後,使用者進行支付,支付完成會有個邏輯叫支付回撥,在回撥裡面需要去做一些業務邏輯。首先來看下同步處理需要花費的時間,如下圖:

上面的下單流程從 3 到 5 都是可以採用非同步流程進行處理,對於使用者來說,支付完成後他就不需要關注後面的流程了。後臺慢慢處理就行了,這樣就能簡化三個步驟,提高回撥的處理時間。

削峰填谷

削峰填谷指的是在大流量的衝擊下,利用 RocketMQ 可以抗住瞬時的大流量,保護系統的穩定性,提升使用者體驗。

在電商行業,最常見的流量衝擊就是秒殺活動了,利用 RocketMQ 來實現一個完整的秒殺業務還是與很多需要做的工作,不在本文的範圍內,後面有機會可以單獨跟大家聊聊。想告訴大家的是像諸如此類的場景可以利用 RocketMQ 來扛住高併發,前提是業務場景支援非同步處理

分散式事務最終一致性

眾所周知,分散式事務有 2PC,TCC,最終一致性等方案。其中使用訊息佇列來做最終一致性方案是比較常用的。

在電商的業務場景中,交易相關的核心業務一定要確保資料的一致性。通過引入訊息佇列 RocketMQ 版的分散式事務,既可以實現系統之間的解耦,又可以保證最終的資料一致性。

資料分發

資料分發指的是可以將原始資料分發到多個需要使用這份資料的系統中,實現資料異構的需求。最常見的有將資料分發到 ES, Redis 中為業務提供搜尋,快取等服務。

除了手動通過訊息機制進行資料分發,還可以訂閱 Mysql 的 binlog 來分發,在分發這個場景,需要使用 RocketMQ 的順序訊息來保證資料的一致性。

RocketMQ 架構

圖片來源阿里雲官方文件

  • Name Server:是一個幾乎無狀態節點,可叢集部署,在訊息佇列 RocketMQ 版中提供命名服務,更新和發現 Broker 服務。就是一個註冊中心。
  • Broker:訊息中轉角色,負責儲存訊息,轉發訊息。分為 Master Broker 和 Slave Broker,一個 Master Broker 可以對應多個 Slave Broker,但是一個 Slave Broker 只能對應一個 Master Broker。Broker 啟動後需要完成一次將自己註冊至 Name Server 的操作;隨後每隔 30s 定期向 Name Server 上報 Topic 路由資訊。
  • 生產者:與 Name Server 叢集中的其中一個節點(隨機)建立長連結(Keep-alive),定期從 Name Server 讀取 Topic 路由資訊,並向提供 Topic 服務的 Master Broker 建立長連結,且定時向 Master Broker 傳送心跳。
  • 消費者:與 Name Server 叢集中的其中一個節點(隨機)建立長連線,定期從 Name Server 拉取 Topic 路由資訊,並向提供 Topic 服務的 Master Broker、Slave Broker 建立長連線,且定時向 Master Broker、Slave Broker 傳送心跳。Consumer 既可以從 Master Broker 訂閱訊息,也可以從 Slave Broker 訂閱訊息,訂閱規則由 Broker 配置決定。

RocketMQ 訊息型別

RocketMQ 支援豐富的訊息型別,可以滿足多場景的業務需求。不同的訊息有不同的應用場景,下面為大家介紹常用的四種訊息型別。

普通訊息

普通訊息是指 RocketMQ 中無特性的訊息。當沒有特殊的業務場景,使用普通訊息就夠了。如果有特殊的場景,就可以使用特殊的訊息型別,比如順序,事務等。

同步傳送

同步傳送:訊息傳送方傳送出去一條訊息,會同步得到服務端返回的結果。

非同步傳送

非同步傳送:訊息傳送方發出去一條訊息,不用等待服務端返回結果,可以接著傳送下一條訊息。傳送方可以通過回撥介面接收服務端響應,並處理響應結果。

單向傳送

單向傳送:訊息傳送方只負責傳送訊息,傳送出去後就不管了,這種方式傳送速度非常快,存在丟失訊息的風險。

順序訊息

順序訊息是指生產者按照一定的先後順序釋出訊息;消費者按照既定的先後順序訂閱訊息,即先發布的訊息一定會先被消費者接收到。

比如資料分發的場景,如果我們訂閱了 Mysql 的 binlog 來進行資料異構。訊息要是沒有順序,就會出現資料錯亂問題。

比如新增一條 id=1 的資料,然後馬上刪除。這樣就產生了兩條訊息。正常的消費順序是先新增,然後刪除,此時資料是沒有的。如果訊息沒有順序,刪除的先被消費了,然後消費新增的,此時資料還在,沒被刪除掉,就會導致不一致。

定時訊息

定時訊息是指訊息具備定時傳送的功能,當訊息傳送到服務端後,不會立即投遞給消費者。而是要等到訊息指定的時間後才會投遞給消費者進行消費。

延遲訊息也就是定時訊息,定時訊息是定在某個時間點進行傳送,比如 2020-11-11 12:00:00 傳送。

延遲訊息一般是在當前傳送時間的基礎上延遲多久進行傳送,比如當前時間是 2020-09-10 12:00:00,延遲 10 分鐘,那麼訊息傳送成功後將在 2020-09-10 12:10:00 進行投遞給消費者。

定時訊息可以在訂單超時未支付自動取消等場景使用。

事務訊息

RocketMQ 提供類似 X/Open XA 的分散式事務功能,通過 RocketMQ 事務訊息能達到分散式事務的最終一致。

互動流程:

圖片來源阿里雲官方文件

  1. 傳送方首先傳送半事務訊息到 RocketMQ 服務端。

  2. RocketMQ 服務端接收到訊息,然後將訊息持久化成功之後,向傳送方返回 Ack 確認訊息已經傳送成功,此時訊息為半事務訊息,不會投遞給消費方。

  3. 收到半事務訊息的 Ack 後,傳送方開始執行本地事務邏輯。

  4. 傳送方根據本地事務執行結果向服務端提交二次確認,如果本地事務執行成則進行訊息的 Commit,如果執行失敗則進行訊息的 Rollback,服務端收到 Commit 狀態則將半事務訊息標記為可投遞,消費方最終將收到該訊息;服務端收到 Rollback 狀態則刪除半事務訊息,消費方將不會收到該訊息。

  5. 如果出現意外情況,步驟 4 沒有進行訊息的二次確認,等待固定時間後服務端將對該訊息發起訊息回查。

  6. 傳送方收到訊息回查後,需要檢查對應訊息的本地事務執行的最終結果。傳送方根據檢查得到的本地事務的最終狀態再次提交二次確認,服務端仍按照步驟 4 對半事務訊息進行操作。

最佳實踐

訊息重試

訊息在消費方消費失敗後,RocketMQ 服務端會重新進行訊息的投遞,知道消費者成功消費訊息,當然重試有次數限制,預設 16 次。

訊息重試在一定程度上保證了訊息不丟失,通過重試來達到最終被消費的目的。需要注意的是消費者在消費的時候一定要等本地業務成功後才能進行 ACK(消費確認),不然就會出現消費失敗,但是已經 ACK,訊息將不會重複投遞。

如果採取非同步消費的方式,需要進行非同步轉同步,等非同步操作完才進行 ACK,具體可以參考我之前寫的一篇文章https://mp.weixin.qq.com/s/Bbh1GDpmkLhZhw5f0POJ2A

最後需要做好對應的監控,如果重試了 4,5 次還是失敗的,基本上後面重試也是失敗的。這個時候需要讓開發人員知道,該人工處理的就人工介入。或者直接監控死信佇列。

訊息過濾

訊息主題,一般用於一類訊息的統一分類。比如訂單主題,但是訂單下的訊息會分為很多種。比如建立訂單,取消訂單等。

不同型別的訊息有不同的業務處理,我們可以統一定義訊息格式,然後通過一個欄位去區分訊息型別來做不同的業務邏輯。不好的點在於所有訊息都會推送到消費方,不能按需消費。

在 RocketMQ 中可以給訊息指定 tag,通過 tag 來區分訊息型別。消費者可以根據 Tag 在 RocketMQ 服務端完成訊息過濾,以確保消費者最終只消費到其關注的訊息型別。

我曾經遇到過一個 tag 沒有正確使用的方式,只有一個 MQ 例項,用 tag 來區分環境。所有訊息都在一個主題中,測試環境消費測試環境的 tag,線上消費線上的 tag。

這種方式的問題在於訊息沒做隔離,線上線下的訊息都在一起。另一個就是 tag 被固定成了環境的區分,無法用於訊息型別場景,導致只能建多個 topic 來承載多個業務訊息型別。

消費模式

RocketMQ 消費模式有兩種,叢集消費和廣播消費。

叢集消費:

消費者部署了多個例項我們稱之為一個叢集,叢集消費只會被其中的某一個例項進行消費。

適合大部分的業務場景,大部分的場景我們的訊息只允許被消費一次,而且只能有一個消費者去消費,比如支付回撥場景,如果一個訊息被多個例項同時消費,那麼就會出現同時去修改訂單狀態,同時去扣減庫存的情況。

廣播消費:

廣播消費會讓叢集中每個例項都消費一次。

比如我們使用了本地快取,當資料變更的時候,我們需要重新整理每個節點本地的快取,所以每個節點都需要收到訊息。

消費冪等

冪等問題,無論是在 API 請求場景還是在訊息消費場景,都會遇到。一條訊息不能重複消費多次這個肯定是要保證的,因為我們不能保證訊息傳送方不傳送多次,也不能保證訊息不重複投遞。

RocketMQ 的 Exactly-Once 投遞語義,就是用於解決冪等問題。Exactly-Once 是指傳送到訊息系統的訊息只能被消費端處理且僅處理一次,即使生產端重試訊息傳送導致某訊息重複投遞,該訊息在消費端也只被消費一次。

最佳的冪等處理方式還是需要有一個唯一的業務標識,雖然每條訊息都有 MessageId,但是不建議用 MessageId 來做冪等判斷,在傳送訊息的時候,可以為每條訊息設定一個 MessageKey,這個 MessageKey 就可以用來做業務的唯一標識。

關於冪等怎麼處理,就不細講了。可以參考我之前寫的一篇文章https://mp.weixin.qq.com/s/9fhqnbeXPz7-7x0Eadd8DA,通用的冪等實現方案。

本地事務訊息封裝

上面介紹了事務訊息,RocketMQ 的事務訊息採用了二階段提交的方式。並且結合了訊息反查的機制來確保最終一致性。

從使用層面來說,每個業務場景都要去實現一個反查的邏輯,有點煩。

下面介紹另一種經常被使用的方式,就是本地事務訊息。本地訊息表這個方案最初是 ebay 提出的,本地事務訊息需要在服務對應的資料庫中建立一個訊息表,傳送訊息的時候不是真正的將訊息傳送給 MQ,而是往訊息表中插入一條訊息資料。

插入的動作跟本地的業務邏輯是同一個事務,如果本地事務執行成功,訊息才會落表成功,才會傳送給 MQ, 本地事務失敗,訊息資料回滾。

然後需要有一個專門的程式去拉取訊息表中未傳送的訊息投遞給 MQ,如果投遞失敗,可以一直重試,直到成功或者人工介入。

訊息寫到訊息表,然後會一直給 MQ 傳送,這個步驟沒問題。如果 MQ 收到訊息後,訊息還在 PageCache 中的時候,Broker 當機了,這個時候是會出現訊息丟失。當然你也可以使用同步刷盤等方式來避免丟失。假如我們就是非同步刷盤,有辦法保證訊息不丟失嗎?

前面我們提到,RocketMQ 的事務訊息會有回查的機制,訊息表的方式,也需要有一個機制來保證訊息被消費了,否則就需要不斷的重試去傳送訊息,直到訊息被消費。

在訊息表中需要有一個欄位來標識當前這條訊息的狀態,比如 未傳送,已傳送,已消費。當訊息還是未傳送的時候就會被髮送到 MQ, 如果傳送成功了,狀態就是已傳送。但是過了幾分鐘,狀態還是已傳送,這個時候就要去做一些動作了。

這個場景下,有可能是消費者跟不上生產的速度,訊息堆積了,導致訊息一直沒被消費。另一種可能就是訊息是不是丟失了?

可以獲取對應的訊息堆積資料來判斷是否訊息堆積了,如果不是就重新傳送訊息給 MQ,知道訊息被消費。

問題是訊息被消費了,我怎麼知道?

像我是用的雲服務,是有對應的 Open API 可以直接查詢訊息軌跡。開源的應該也有,沒有仔細去研究,跟商業版應該差不多。

根據訊息軌跡就可以知道訊息有沒有被消費,到此為止流程結束。訊息傳送給 MQ 如果失敗會重試,訊息如果長時間沒消費,也會重新傳送,即使最後進入了死信佇列,也可以通過死信佇列的監控來人工干預,一定會是最終一致性。

跟自帶的事務訊息比,本地訊息表的方式不需要實現回查邏輯,但是要增加訊息表,同時也要配套各種傳送,檢查等邏輯,也挺麻煩了。特別是當訊息量大的時候,如何快速的將訊息表中的訊息傳送出去,也需要做很多處理,簡單的查表輪詢在量大的情況下不太適用。

兩種方式都可以使用,能實現我們要的目的即可。

參考程式碼

本地事務訊息相關的程式碼可以微信搜尋「猿天地」,回覆關鍵字「kitty」獲取即可。

碼字不易,可以的話來個三連擊,感謝!

關於作者:尹吉歡,簡單的技術愛好者,《Spring Cloud 微服務-全棧技術與案例解析》, 《Spring Cloud 微服務 入門 實戰與進階》作者, 公眾號猿天地發起人。

我整理了一份很全的學習資料,感興趣的可以微信搜尋「猿天地」,回覆關鍵字 「學習資料」獲取我整理好了的 Spring Cloud,Spring Cloud Alibaba,Sharding-JDBC 分庫分表,任務排程框架 XXL-JOB,MongoDB,爬蟲等相關資料。

相關文章