“萬惡”的 Kafka 再平衡:2招解決不必要的再平衡

“萬惡”的 Kafka 再平衡:2招解決不必要的再平衡

最近在維護一個Kafka消息轉發的項目的時候,遇到一個很詭異的問題:如果消費者 poll() 方法的參數 timeout 設置太短,可能會導致再平衡失敗,原因是在 timeout 規定的時限內如果沒有成功再平衡,就會導致消費者心跳線程 disable, 此時消費者將無法發送心跳給協調者所在 Broker, 消費者也就無法重新進入消費者組,這個消費者就會崩潰了。再平衡的失敗,就像蝴蝶效應一樣,引發了一系列後續錯誤,最終導致消費者不可用,程序崩潰。

儘管 Kafka 再平衡(Rebalance)賦予消費者組動態增加、刪除消費者實例的能力,讓其以高伸縮性和高容錯性聞名遐邇,但是再平衡的失敗將會導致一系列的連鎖反應,直至消費者崩潰,嚴重影響了消費者組的消費能力。


那到底什麼是再平衡?是什麼引發了再平衡?再平衡的弊端是什麼?如何避免不必要的再平衡呢?為什麼再平衡這麼招人恨呢?


不著急,且聽我慢慢道來。

1. 再平衡到底是什麼鬼?

再平衡(Rebalance)就是讓一個消費者組(Consumer Group)的所有消費者(Consumer)實例就如何分配所訂閱的主題的所有分區達成共識的過程。簡單來說,再平衡其實就是中間人,給消費者組中的消費者實例與分區牽線搭橋,一一配對(或者一對多配對),讓消費者實例有的消費。從這個角度看,再平衡真的是功德無量,為不少“單身狗”找到了歸宿,不僅讓消費者找到消費的對象,也讓分區找到歸宿,解決了大量的“剩男剩女”問題。


在再平衡過程中,所有的消費者實例共同參與,在協調者(Coordinator)的幫助之下,完成訂閱主題的分區的分配。這裡的協調者專門為消費者組服務,主要負責執行再平衡、提供位移管理以及組成員管理等,它的角色很像月老,只要涉及到再平衡的方方面面,事無鉅細,它都要插手,可以說是個很盡職的月老了。


每一個消費者組都有一個對應的協調者,這個協調者位於 Broker,所有的 Broker 在啟動時,都會創建和開啟相應的協調者組件。在消費者啟動的時候,會向消費者組對應的協調者所在的 Broker 發送各種請求,然後由協調者負責執行消費者組的註冊、成員管理記錄等元數據管理操作。 當消費者在提交位移的時候,其實也是在向協調者所在的 Broker 提交位移。

2. 再平衡到底做了什麼“惡”?

“萬惡“的再平衡到底做了什麼”惡“?為什麼會這麼招人恨呢?


如果再平衡能夠好好做它的月老,每天「千里姻緣一線牽」,成全消費者和分區的好事,那它就真的是功德無量了。凡事就怕一個但是,但是呢,再平衡本身有很多臭毛病,讓人哭笑不得。


首先,再平衡的過程會停止消費者組所有成員的消費過程。在再平衡的過程中,所有的消費者實例全部都會停止消費,stop the world(STW),直到再平衡完成,才會恢復消息進程,這個過程會對消費端的 TPS 造成很大影響。


其次,再平衡的過程效率很低。再平衡的時候所有的消費者實例均需要參與其中,全部重新分配所有分區。這個過程其實做了很多無用功,事實上,儘量減少各個消費者實例所分配的分區的變動才是最優的方案,而不是全部重新洗牌,這樣的話,就會節省很多資源和時間。例如,消費者1原本負責消費分區1、2、3,那麼再平衡之後也應該接著消費1、2、3,而不是重新分配其他分區,這樣的話,消費者1連接這些分區所在的Broker的 TCP 連接就可以繼續用了,不僅節省了資源,也減少了時間。

局部性原理對提升系統性能特別重要。


最後,再平衡的速度實在是太慢了。上百個消費者實例的消費者組再平衡一次很可能需要幾個小時,這是難以忍受的。


值得注意的是,社區提出了有粘性的分區分配策略,利用局部性原理解決再平衡過程中的效率問題。每次再平衡的過程中,該策略會盡可能保留之前的分配方案,儘量實現分區分配的最小變動。


那到底是誰打開了潘多拉魔盒,釋放出再平衡的惡呢?

3. 是誰引爆了再平衡“惡魔”的導火索?

消費者組滿足某些特定的條件的時候,就會觸發再平衡。一般來說,觸發再平衡的條件有以下三個:

  • 消費者組成員數發生變更。當消費者組中有新的消費者實例加入或者是有消費者實例因為崩潰而離開消費者組,都會觸發再平衡。這是因為,此時消費者組中的消費者數量發生變化,與分區的對應關係發生了改變,因此需要進行再平衡重新分配。
  • 消費者訂閱的主題發生變更。消費者訂閱的主題的名稱或者主題的數量發生變化都會觸發再平衡,因為主題的變更打破了消費者與分區的對應關係,需要進行再平衡重新分配。
  • 訂閱主題的分區數發生變化。和訂閱主題的變更一樣,訂閱主題的分區數的變化打破了消費者與分區的分配關係,需要進行再平衡重新分配。


其中後面兩個再平衡的觸發條件是不可避免的,而且這兩個條件的發生頻率並沒有那麼高,也不太會造成一些比較難以追蹤的 bug,所以其實這兩個條件可以不做處理。


而消費者組成員數的變化引發再平衡則是非常常見的。當消費者實例增加的時候,一般都是我們啟動一個新的具有相同 GroudID 的消費者實例入組的時候。此時,協調者將會執行再平衡,為消費者組重新分配分區。通常來說,增加消費者主要是為了提高消費者組的消費能力,因此這種情況其實也是不需要處理的。


但是,系統中某個消費者實例可能因為某些意外的情形而被協調者認為該消費者已經掛掉了,從而離開消費者組,這種情況其實我們是需要去避免的,因為消費者實例的減少會降低消費端的消費能力,而且這種情況往往還伴隨著一定的異常和錯誤,因此這種情況我們是需要極力避免的。

4. 如何避免不必要的再平衡呢 ?

不必要的再平衡都有那些情況呢?


第一類不必要的再平衡是因為沒有消費者的心跳線程沒有及時發送心跳給協調者,導致協調者認為該消費者已經掛掉了,所以將其踢出消費者組。

這裡所謂的及時是指發送心跳的時間週期需要限制在消費者參數 session.timeout.ms (默認值 10 s)規定的時間範圍內,即每隔session.timeout.ms 規定的時間需要發送一次心跳。除此之外,消費者端參數 heartbeat.interval.ms 允許用戶控制發送心跳請求的頻率,這個值越小,消費者發送心跳請求的頻率就越高。儘管頻繁的發送心跳請求會消耗一定的網絡帶寬,但是卻能更加快速地知道當前是否需要再平衡剔除不可用的消費者。


合理設置這兩個參數可以避免此類再平衡,以下是一些推薦參數:

  • 設置 session.timeout.ms = 6s
  • 設置 heartbeat.interval.ms = 2s
  • 要保證 Consumer 實例在被判定為“dead”之前,能夠發送至少 3 輪的心跳請求,即 session.timeout.ms >= 3 * heartbeat.interval.ms。


第二類非必要 Rebalance 是 Consumer 消費時間過長導致的。這裡的消息時間過長其實指的主要是消息處理的時間過長。消息處理時間過長會導致兩次 poll() 方法之間調用的時間過長,當這個時間超過 max.poll.interval.ms(默認值 5 分鐘) 規定的時間之後,消費者就會主動發起離組的請求,協調者就會發起新的一輪再平衡。因此,如果消息處理的過程時間非常長,那就需要將這個參數設置的大一點,比消息處理的時間稍微長一點,這樣就不會因為消息處理時間過長而導致再平衡了。


除了以上這兩種情況,其實消費者如果出現頻繁的 Full GC 也會導致非預期的再平衡,因此排查問題的時候也需要考慮一下這種情況。

5. 總結

作為一個“月老”,再平衡為分區和消費者組的消費付出了太多,然而,再平衡的壞脾氣卻讓它屢屢犯錯,頻繁的可以避免的再平衡嚴重地影響了消費者的消費能力。而深入理解再平衡觸發的條件和原理能夠讓我們更好地理解再平衡,所謂「打蛇打七寸」,抓住再平衡發生的命脈,我們就可以很好的解決那些可以避免的再平衡,從而提高消費者組的消費能力。


分享到:


相關文章: