RocketMQ 必知概念

zhzcc發表於2024-10-06

延遲訊息

延遲等級

官方預設設定了 18 哥延遲等級

1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

傳送延遲訊息:按照預設順序 1-18 數字就對應上面的延遲時間

Message msg = new Message (TOPIC, TAG, "OrderID199", "ok", getBytes(StandardCharsets.UTF_8));
//設定延遲等級
msg.setDelayTimeLevel(3);
producer.send(msg);

基本原理

延遲訊息都會被儲存到 RocketMQ 的一個內部 Topic:SCHEDULE_TOPIC_XXXX 中

SCHEDULE_TOPIC_XXXX 共有 18 個 MessageQueue:

  • 對應延遲訊息的 18 個等級,根據指定的 DelayTimeLevel 來決定選擇哪個 MessageQueue
  • 有一個定時任務,每 100 ms 執行一次判斷 SCHEDULE_TOPIC_XXXX Topic 中的 MessageQueue 的訊息是否到達延遲時間
  • 若到達延遲時間,將 SCHEDULE_TOPIC_XXXX 中的訊息投遞到訊息最初需要投遞的 Topic 之中

為什麼不支援任意時間?

RocketMQ 並不支援任意時間的延遲,可能的主要原因就是如果提供任意時間,就會涉及到訊息的排序,會有一定的效能損耗

事務訊息

RocketMQ 採用了 2PC 的思想來實現了提交事務訊息,同時增加一個補償邏輯來處理二階段超時或失敗的訊息

image-20241006210120038

基本流程

第一階段:

  • 傳送 Message,Half Message ,即半事務訊息
  • 此型別的 Message 是不會被 Consumer 消費的

第二階段:如果半事務訊息投遞成功,則會開始執行本地事務

分為如下三種 Case:

  • 本地事務執行成功:會為 Broker 傳送 commit 訊息,被 commit 過後的 Message 才能被 Consumer 消費
  • 本地事務執行失敗:
    • 會為 Broker 傳送 rollback 訊息,Broker 則會將剛剛投遞的半事務訊息刪除,從而保證上下游資料的一致性
    • 如果 Producer 例項或者網路出現問題,Producer 沒能及時 de 將本地事務執行的結果通知 Broker,Broker 會透過掃描發現某條 Message 長時間處於半事務狀態,Broker 會主動 de 給 Producer 詢問此 Message 對應的事務狀態

基本設計

採用 2PC 兩階段設計:

image-20241006133353449


將 Message 原本真實的 Topic 和 MessageQueue 進行備份

  • 存入到 PROPERTY_REALTOPC、PROPERTY_REAL_QUEUE_ID 中

將訊息投遞到一個內部 Topic 中 RMQ_SYS_TRANS_HALF_TOPIC,該佇列專門儲存事務訊息

所有的 Half Message 全部都寫入到 queueId 為 0 的 MessageQueue,因為一個 Topic 下只有 1 個 MessageQueue:

  • 這個 Topic 下的所有 Message 就是全域性有序的,ta 們會按照先來後到的順序被消費

如果本地事務執行成功進行 Commit,則將 RMQ_SYS_TRANS_HALF_TOPIC 佇列中的訊息投遞到真實的 Topic 中,供後續流程執行

  • 並刪除這條 Half Message,但刪除也是假刪除,只是給 Message 打上一個刪除的 tag

如果本地事務執行失敗進行 rollback,則直接刪除這條 Half Message,但刪除也是假刪除

如果本地事務吃吃沒有返回結果(預設時間 6s),則會觸發事務回查機制

  • 執行回查之前需要校驗檢查次數是否達到了最大值(需要手動設定,沒有預設值)
  • 或者是當前 Half Message 存在是否超過了 Message 儲存的上限,即 3 天
  • 如果滿足上面條件中的一種 Half Message 會被放進 TRANS_CHECK_MAX_TOPIC Topic 中
  • 一旦判定為需要執行事務回查邏輯,那麼當前這條 Half Message 就算已經被消費了
  • 在沒有達到最大的校驗次數之前,都還需要將其投遞到事務佇列當中,以便下次重試時再次執行 Check 邏輯
  • 如果回查成功,則刪除投遞的 Half Message

訊息重試

重試時間

訊息消費失敗後,並不會立即重試,而是一個遞增的時間間隔來進行重試的,重試次數預設為 16 次

只比延遲訊息的時間間隔等級少了前兩個,延遲訊息總共有 18 個等級,而訊息重試使用了延遲訊息的第 3-18 等級

10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

基本原理

重試的 Message,RocketMQ 的做法並不是將其投遞迴原來的 Topic,而是重試佇列

每個 ConsumerGroup 都有自己的重試佇列:

  • 其名稱是由特定的字首拼接上 ConsumerGroup 所組成,預設 %RETR% + 消費者組名稱
  • 所有在 Consumer 啟動時,就會同時消費其 ConsumerGroup 對應的重試佇列和普通佇列

消費失敗的 Message,Consumer 會將其投回 Broker:

  • 相當於這條 Message 已經被消費掉了,之後重試的只是內容相同,但實際不是同一個的 Message
  • 然後會校驗重試的次數,如果達到 16 次,則會進入死信佇列,組成為 %DLQ% + 消費者組名稱
  • 未達到最大重試次數,則會根據重試間隔時間等級將其投遞到延遲佇列 SCHEDULE_TOPIC_XXXX 中
  • 然後等到了延遲等級對應的時間後,在投遞到 ConsumerGroup 所對應的重試佇列當中,供後續消費

訊息儲存

整體架構

RocketMQ 的混合性儲存結構(多個 Topic 的訊息實體內容都儲存於一個 CommitLog中)

針對 Producer 和 Consumer 分別採用了資料和索引部分相分離的儲存結構

Producer 傳送訊息至 Broker 端,然後 Broker 端使用同步或者非同步的方式對訊息刷盤持久化,儲存至 CommitLog 中

image-20241006191349416


核心步驟:

  1. 首先,生產者根據 topic 傳送訊息,訊息儲存在 commitLog中,1 G一個檔案,當檔案滿了,寫入下一個檔案
  2. 其次,ReputMessageService 重寫訊息服務執行 2 個分發操作:
    • 建立 ConsumerQueue 邏輯消費佇列:
      • 引數:commitLogOffset 物理偏移量、msgSize 訊息長度、tagsCode tag 雜湊
    • 建立 IndexFile 索引檔案:
      • 以建立時的時間戳命名
  3. 最後,消費者根據 topic、tag 拉取訊息消費,根據 key 查詢訊息

重要檔案

commitLog 訊息日誌:

  • 訊息主體以及後設資料的儲存主體,儲存 Producer 端寫入的訊息主體內容

consumequeue 邏輯消費佇列:

  • 儲存了 commitLog 的起始物理 offset,目的是提高消費的效能

indexFile 索引檔案:

  • 提供了一種可以透過 key 或者時間區間來查詢訊息的方法

consumequeue 檔案:

consumequeue 檔案採取定長設計,每一個條目共 20 個位元組,分別為:

  • 8 位元組的 commmitLog 物理偏移量
  • 4 位元組 的訊息長度
  • 8 位元組 tag hashcode

單個檔案由 30w 個條目組成,可以像陣列一樣隨機訪問每一個條目,每個 ConsumeQueue 檔案大小約 5.72M

  • 預設一個 topic 對應 4 個 queueId,即 4 個 messageQueue

每個 messageQueue 資料夾下有多個 consumeQueue,所以:messageQueue 1 :N consumeQueue

image-20241006192406854

通訊機制

通訊架構圖

image-20241006192645688

基本通訊流程如下:

  • Broker 啟動後需要完成一次將自己註冊至 NameServer 的操作,隨後每隔 30s 時間定時向 NameServer 上報 Topic 路由資訊
  • 訊息生成者 Producer 作為客戶端傳送訊息時,需要根據訊息的 Topic 從本地快取的 TopicPublishInfoTable 獲取路由資訊
    • 如果沒有則更新路由資訊會從 NameServer 上重新拉取,同時 Producer 會預設每隔 30s 向 NameServer 拉取一次路由資訊
  • 訊息生產者 Producer 根據獲取的路由資訊選擇一個佇列(MessageQueue) 進行訊息傳送
    • Broker 作為訊息的接收者接收訊息並落盤儲存
  • 訊息消費者 Consumer 根據獲取的路由資訊,並再完成客戶端的負載均衡後,選擇其中的某一個或者某幾個訊息佇列來拉取訊息並進行消費

為了實現客戶端與伺服器之間高效的資料請求與接收:

  • RocketMQ-Remoting 包自定義了通訊協議並在 Netty 的基礎之上擴充套件了通訊模組

相關文章