什麼是分區再均衡
分區的所有權從一個消費者轉移到另一個消費者,這樣的行爲被稱爲分區再均衡(Rebalance)。Rebalance 實現了消費者羣組的高可用性和伸縮性。
Rebalance 本質上是一種協議,規定了一個 Consumer Group 下的所有 Consumer 如何達成一致,來分配訂閱 Topic 的每個分區。比如某個 Group 下有 20 個 Consumer 實例,它訂閱了一個具有 100 個分區的 Topic。正常情況下,Kafka 平均會爲每個 Consumer 分配 5 個分區。這個分配的過程就叫 Rebalance。
當在羣組裏面 新增/移除消費者 或者 新增/移除 kafka 集羣 broker 節點 時,羣組協調器 Broker 會觸發再均衡,重新爲每一個 Partition 分配消費者。Rebalance 期間,消費者無法讀取消息,造成整個消費者羣組一小段時間的不可用。
何時生分區再均衡
分區再均衡的觸發時機有三種:
-
消費者羣組成員數發生變更,新 Consumer 加入羣組或者離開羣組,或者是有 Consumer 實例崩潰被“踢出”羣組。
- 新增消費者。Consumer 訂閱主題之後,第一次執行 poll 方法
- 移除消費者。執行
consumer.close()
操作或者消費客戶端宕機,就不再通過 poll 向羣組協調器發送心跳了,當羣組協調器檢測次消費者沒有心跳,就會觸發再均衡。
-
訂閱主題數發生變更。Consumer Group 可以使用正則表達式的方式訂閱主題,比如
consumer.subscribe(Pattern.compile(“t.*c”))
就表明該 Group 訂閱所有以字母 t 開頭、字母 c 結尾的主題。在 Consumer Group 的運行過程中,你新創建了一個滿足這樣條件的主題,那麼該 Group 就會發生 Rebalance。 -
訂閱主題的分區數發生變更
。Kafka 當前只能允許增加一個主題的分區數。當分區數增加時,就會觸發訂閱該主題的所有 Group 開啓 Rebalance。
- 新增 broker。如重啓 broker 節點
- 移除 broker。如 kill 掉 broker 節點。
分區再均衡的過程
Rebalance 是通過消費者羣組中的稱爲“羣主”消費者客戶端進行的。
(1)選擇羣主
當消費者要加入羣組時,會向羣組協調器發送一個 JoinGroup
請求。第一個加入羣組的消費者將成爲“羣主”。羣主從協調器那裏獲取羣組的活躍成員列表,並負責給每一個消費者分配分區。
所謂協調者,在 Kafka 中對應的術語是 Coordinator,它專門爲 Consumer Group 服務,負責爲 Group 執行 Rebalance 以及提供位移管理和組成員管理等。具體來講,Consumer 端應用程序在提交位移時,其實是向 Coordinator 所在的 Broker 提交位移。同樣地,當 Consumer 應用啓動時,也是向 Coordinator 所在的 Broker 發送各種請求,然後由 Coordinator 負責執行消費者組的註冊、成員管理記錄等元數據管理操作。
(2)消費者通過向被指派爲羣組協調器(Coordinator)的 Broker 定期發送心跳來維持它們和羣組的從屬關係以及它們對分區的所有權。
(3)羣主從羣組協調器獲取羣組成員列表,然後給每一個消費者進行分配分區 Partition。有兩種分配策略:Range 和 RoundRobin。
- Range 策略,就是把若干個連續的分區分配給消費者,如存在分區 1-5,假設有 3 個消費者,則消費者 1 負責分區 1-2,消費者 2 負責分區 3-4,消費者 3 負責分區 5。
- RoundRoin 策略,就是把所有分區逐個分給消費者,如存在分區 1-5,假設有 3 個消費者,則分區 1->消費 1,分區 2->消費者 2,分區 3>消費者 3,分區 4>消費者 1,分區 5->消費者 2。
- Sticky Strategy策略
(4)羣主分配完成之後,把分配情況發送給羣組協調器。
(5)羣組協調器再把這些信息發送給消費者。每個消費者只能看到自己的分配信息,只有羣主知道所有消費者的分配信息。
如何判定消費者已經死亡
消費者通過向被指定爲羣組協調器的 Broker 發送心跳來維持它們和羣組的從屬關係以及它們對分區的所有權關係。只要消費者以正常的時間間隔發送心跳,就被認爲是活躍的。消費者會在輪詢消息或提交偏移量時發送心跳。如果消費者超時未發送心跳,會話就會過期,羣組協調器認定它已經死亡,就會觸發一次再均衡。
當一個消費者要離開羣組時,會通知協調器,協調器會立即觸發一次再均衡,儘量降低處理停頓。
查找協調者
所有 Broker 在啓動時,都會創建和開啓相應的 Coordinator 組件。也就是說,所有 Broker 都有各自的 Coordinator 組件。那麼,Consumer Group 如何確定爲它服務的 Coordinator 在哪臺 Broker 上呢?答案就在我們之前說過的 Kafka 內部位移主題 __consumer_offsets
身上。
目前,Kafka 爲某個 Consumer Group 確定 Coordinator 所在的 Broker 的算法有 2 個步驟。
- 第 1 步:確定由位移主題的哪個分區來保存該 Group 數據:
partitionId=Math.abs(groupId.hashCode() % offsetsTopicPartitionCount)
,即協調者所在分區爲消費者組groupID的哈希值 % _consumer_offsets隊列分區數量。 - 第 2 步:步驟1確定的分區的leader副本所在broker即爲協調者。
分區再均衡的問題
- 首先,Rebalance 過程對 Consumer Group 消費過程有極大的影響。在 Rebalance 過程中,所有 Consumer 實例都會停止消費,等待 Rebalance 完成。
- 其次,目前 Rebalance 的設計是所有 Consumer 實例共同參與,全部重新分配所有分區。其實更高效的做法是儘量減少分配方案的變動。
- 最後,Rebalance 實在是太慢了。
避免分區再均衡
通過前文,我們已經知道了:分區再均衡的代價很高,應該儘量避免不必要的分區再均衡,以整體提高 Consumer 的吞吐量。
分區再均衡發生的時機有三個:
- 組成員數量發生變化
- 訂閱主題數量發生變化
- 訂閱主題的分區數發生變化
後面兩個通常都是運維的主動操作,所以它們引發的 Rebalance 大都是不可避免的。實際上,大部分情況下,導致分區再均衡的原因是:組成員數量發生變化。
有兩種情況,消費者並沒有宕機,但也被視爲消亡:
- 未及時發送心跳
- Consumer 消費時間過長
未及時發送心跳
第一類非必要 Rebalance 是因爲未能及時發送心跳,導致 Consumer 被“踢出”Group 而引發的。因此,你需要仔細地設置session.timeout.ms 和 heartbeat.interval.ms的值。我在這裏給出一些推薦數值,你可以“無腦”地應用在你的生產環境中。
- 設置
session.timeout.ms
= 6s。 - 設置
heartbeat.interval.ms
= 2s。 - 要保證 Consumer 實例在被判定爲“dead”之前,能夠發送至少 3 輪的心跳請求,即
session.timeout.ms
>= 3 *heartbeat.interval.ms
。
將 session.timeout.ms
設置成 6s 主要是爲了讓 Coordinator 能夠更快地定位已經掛掉的 Consumer。畢竟,我們還是希望能儘快揪出那些“尸位素餐”的 Consumer,早日把它們踢出 Group。希望這份配置能夠較好地幫助你規避第一類“不必要”的 Rebalance。
Consumer 消費時間過長
第二類非必要 Rebalance 是 Consumer 消費時間過長導致的。我之前有一個客戶,在他們的場景中,Consumer 消費數據時需要將消息處理之後寫入到 MongoDB。顯然,這是一個很重的消費邏輯。MongoDB 的一丁點不穩定都會導致 Consumer 程序消費時長的增加。此時,max.poll.interval.ms
參數值的設置顯得尤爲關鍵。如果要避免非預期的 Rebalance,你最好將該參數值設置得大一點,比你的下游最大處理時間稍長一點。就拿 MongoDB 這個例子來說,如果寫 MongoDB 的最長時間是 7 分鐘,那麼你可以將該參數設置爲 8 分鐘左右。
GC
如果你按照上面的推薦數值恰當地設置了這幾個參數,卻發現還是出現了 Rebalance,那麼我建議你去排查一下Consumer 端的 GC 表現,比如是否出現了頻繁的 Full GC 導致的長時間停頓,從而引發了 Rebalance。爲什麼特意說 GC?那是因爲在實際場景中,我見過太多因爲 GC 設置不合理導致程序頻發 Full GC 而引發的非預期 Rebalance 了。
Kafka Rebalance 圖片表示
Rebalance過程
分爲2步:Join和Sync
1 Join, 顧名思義就是加入組。這一步中,所有成員都向coordinator發送JoinGroup請求,請求入組。一旦所有成員都發送了JoinGroup請求,coordinator會從中選擇一個consumer擔任leader的角色,並把組成員信息以及訂閱信息發給leader——注意leader和coordinator不是一個概念。leader負責消費分配方案的制定。
2 Sync,這一步leader開始分配消費方案,即哪個consumer負責消費哪些topic的哪些partition。一旦完成分配,leader會將這個方案封裝進SyncGroup請求中發給coordinator,非leader也會發SyncGroup請求,只是內容爲空。coordinator接收到分配方案之後會把方案塞進SyncGroup的response中發給各個consumer。這樣組內的所有成員就都知道自己應該消費哪些分區了。
還是拿幾張圖來說明吧,首先是加入組的過程:
值得注意的是, 在coordinator收集到所有成員請求前,它會把已收到請求放入一個叫purgatory(煉獄)的地方。
然後是分發分配方案的過程,即SyncGroup請求:
4種Reblance場景
1 新成員加入組(member join)
2 組成員崩潰(member failure)
前面說過了,組成員崩潰和組成員主動離開是兩個不同的場景。因爲在崩潰時成員並不會主動地告知coordinator此事,coordinator有可能需要一個完整的session.timeout週期才能檢測到這種崩潰,這必然會造成consumer的滯後。可以說離開組是主動地發起rebalance;而崩潰則是被動地發起rebalance。okay,直接上圖:
3 組成員主動離組(member leave group)
4 提交位移(member commit offset)