1. 程式人生 > 其它 >RocketMQ 客戶端負載均衡機制詳解及最佳實踐

RocketMQ 客戶端負載均衡機制詳解及最佳實踐

作者:玄珏

前言

本文介紹 RocketMQ 負載均衡機制,主要涉及負載均衡發生的時機、客戶端負載均衡對消費的影響(訊息堆積/消費毛刺等)並且給出一些最佳實踐的推薦。

負載均衡意義

上圖是 RocketMQ 的訊息儲存模型:訊息是按照佇列的方式分割槽有序儲存的。RocketMQ 的佇列模型使得生產者、消費者和讀寫佇列都是多對多的對映關係,彼此之間都可以無限水平擴充套件。對比傳統的訊息佇列如 RabbitMQ 是很大的優勢。尤其是在流式處理場景下有天然優勢,能夠保證同一佇列的訊息被相同的消費者處理,對於批量處理、聚合處理更友好。

消費者消費某個 topic 的訊息等同於消費這個 topic 上所有佇列的訊息(上圖中 Consumer A1 消費佇列 1,Consumer A2 消費佇列 2、3)。

所以,要保證每個消費者的負載儘量均衡,也就是要給這些消費者分配相同數量的佇列,並保證在異常情況下(如客戶端宕機)佇列可以在不同消費者之間遷移。

負載均衡機制解析

負載均衡時機

負載均衡是客戶端與服務端互相配合的過程,我們先綜合服務端和客戶端的職責回答第一個問題:何時會發生負載均衡。

  • 客戶端主動負載均衡

上圖是 RocketMQ 客戶端相關類的結構,其中 MQClientInstance 負責和服務端的互動以及底層服務的協調,這其中就包括負載均衡。

MQClientInstance 中有兩個相關的方法 rebalanceImmediately 和 doRebalance,我們分析負載均衡的時機只要找到何時呼叫這兩個方法即可:

  1. 啟動時立即進行負載均衡;
  2. 定時(預設 20s)負載均衡一次。
  • 服務端通知負載均衡

服務端通知客戶端進行負載均衡也是通過 MQClientInstance#rebalanceImmediately 方法實現的,我們同樣在服務端程式碼中尋找相關呼叫。

分析以上幾個方法可以得出結論,在如下場景服務端會主動通知客戶端觸發負載均衡:

  1. 客戶端上下線
    • 上線
    1. 新客戶端傳送心跳到服務端
    • 下線
    1. 客戶端傳送下線請求到服務端
    2. 底層連線異常:響應 netty channel 的 IDLE/CLOSE/EXCEPTION 事件
  1. 訂閱關係變化:訂閱新 topic 或有舊的 topic 不再訂閱

負載均衡策略

前文已經介紹了負載均衡實際是變更消費者負責處理的佇列數量,這裡每次需要變更的佇列數量和受到影響的客戶端數量是由負載均衡策略決定的。

我們來分析一下比較常見的負載均衡策略:

  • 平均分配

平均分配(AllocateMessageQueueAveragely)是預設的負載均衡策略:

如果我們有 4 個客戶端,24 個佇列,當第二個客戶端下線時:

以預設的負載均衡策略(AllocateMessageQueueAveragely)為例,重新分配佇列數量為 8。

預設的負載均衡策略能將佇列儘量均衡的分配到每個客戶端,但是每次負載均衡重新分配佇列數量較多,尤其是在客戶端數量很多的場景。

客戶端 佇列分配變化 佇列數變化
Client1 1~6 -> 1~8 6 -> 8
Client2 7~12 -> - 6 -> 0
Client3 13~18 -> 9~16 6 -> 8
Client4 19~24 -> 17~24 6 -> 8
  • 一致性雜湊

基於一致性雜湊演算法的負載均衡策略(AllocateMessageQueueConsistentHash)每次負載均衡會重新分配儘可能少的佇列數量,但是可能會出現負載不均的情況。

客戶端 佇列分配變化 佇列數變化
Client1 1~6 -> 1~9 6 -> 9
Client2 7~12 -> - 6 -> 0
Client3 13~18 -> 10~18 6 -> 9
Client4 19~24 -> 19~24 6 -> 8

負載均衡對消費的影響

我們以一個真實的線上場景來舉例:

下圖中綠色的線代表傳送 tps,黃色的線代表消費 tps,我們很容易發現在 21:00 和 21:50 分左右存在消費毛刺。

這兩個時間點在進行應用釋出,根據我們上文的分析某個消費者下線後同組的其他消費者感知這一變化需要一定時間,導致有秒級的消費延遲產生。在釋出結束後消費者快速處理堆積的訊息,可以發現消費速度有一個明顯的上漲。

這個例子展示了下線時由於負載均衡帶來了短暫的訊息處理延遲,新的消費者會從服務端獲取消費位點繼續之前的消費進度。如果消費者異常宕機或者沒有呼叫 shutdown 優雅下線,沒有上傳自己的最新消費位點,會使得新分配的消費者重複消費。

這裡我們總結下負載均衡對消費的影響,當某個客戶端觸發負載均衡時:

  1. 對於新分配的佇列可能會重複消費,這也是官方要求消費要做好冪等的原因;
  2. 對於不再負責的佇列會短時間消費停止,如果原本的消費 TPS 很高或者正好出現生產高峰就會造成消費毛刺。

最佳實踐

避免頻繁上下線

為了避免負載均衡的影響應該儘量減少客戶端的上下線,同時做好消費冪等。

同時在有應用重啟或下線前要呼叫 shutdown 方法,這樣服務端在收到客戶端的下線請求後會通知客戶端及時觸發負載均衡,減少消費延遲。

選擇合適的負載均衡策略

需要根據業務需要靈活選擇負載均衡策略:

  • 需要保證客戶端的負載儘可能的均衡:選擇預設的平均分配策略;
  • 需要降低應用重啟帶來的消費延遲:選擇一致性雜湊的分配策略。

當然還有其他負載均衡策略由於時間關係不一一介紹了,留給讀者自行探索。

保證客戶端訂閱一致

RocketMQ 的負載均衡是每個客戶端獨立進行計算,所以務必要保證每個客戶端的負載均衡演算法和訂閱語句一致。

  • 負載均衡策略不一致會導致多個客戶端分配到相同佇列或有客戶端分不到佇列;
  • 訂閱語句不一致會導致有訊息未能消費。

RocketMQ 5.0 訊息級別負載均衡

為了徹底解決客戶端負載均衡導致的重複消費和消費延遲問題,RocketMQ 5.0 提出了訊息級別的負載均衡機制。

同一個佇列的訊息可以由多個消費者消費,服務端會確保訊息不重不漏的被客戶端消費到:

訊息粒度的負載均衡機制,是基於內部的單條訊息確認語義實現的。消費者獲取某條訊息後,服務端會將該訊息加鎖,保證這條訊息對其他消費者不可見,直到該訊息消費成功或消費超時。因此,即使多個消費者同時消費同一佇列的訊息,服務端也可保證訊息不會被多個消費者重複消費。

在 4.x 的客戶端中,順序消費的實現強依賴於佇列的分配。RocketMQ 5.0 在訊息維度的負載均衡的基礎上也實現了順序消費的語意:不同消費者處理同一個訊息組內的訊息時,會嚴格按照先後順序鎖定訊息狀態,確保同一訊息組的訊息序列消費。

如上圖所述,佇列 Queue1 中有 4 條順序訊息,這 4 條訊息屬於同一訊息組 G1,儲存順序由 M1 到 M4。在消費過程中,前面的訊息 M1、M2 被 消費者Consumer A1 處理時,只要消費狀態沒有提交,消費者 A2 是無法並行消費後續的 M3、M4 訊息的,必須等前面的訊息提交消費狀態後才能消費後面的訊息。

如果您對RocketMQ感興趣,歡迎掃描下方二維碼加入釘釘群一起溝通交流~

點選閱讀原文,進入官網瞭解更多詳情~