session.timeout.ms heartbeat.interval.ms參數的區別

注:本文是從https://www.cnblogs.com/hapjin/p/10926882.html處摘抄,可直接跳轉至原頁面。

最近碰到一個問題,多個業務往向一個Kafka topic發送消息,有些業務的消費量很大,有些業務的消息量很小。因Kafka尚未較好地支持按優先級來消費消息,導致某些業務的消息消費延時的問題。一種簡單的解決方案是再增加幾個Topic,面對一些系統遺留問題,增加Topic帶來的是生產者和消費者處理邏輯複雜性。一種方法是使用Kafka Standalone consumer,先使用consumer.partitionFor("TOPIC_NAME")獲取topic下的所有分區信息,再使用 consumer.assign(partitions)顯示地爲consumer指定消費分區。另一種方法是基於consumer group 自定義Kafka consumer的分區分配策略,那這時候就得對Kafka目前已有的分區分配策略有所瞭解,並且明白什麼時候、什麼場景下觸發rebalance?

Kafka consumer要消費消息,哪些的分區的消息交給哪個consumer消費呢?這是consumer的分區分配策略,默認有三個:rangeround-robinsticky

因爲一個topic往往有多個分區,而我們又會在一個consumer group裏面創建多個消費者消費這個topic,因此:就有了一個問題:哪些的分區的消息交給哪個consumer消費呢?這裏涉及到三個概念:consumer groupconsumer,以及group coordinator。conusmer分區分配是通過組管理協議來實施的:具體如下:

在正常情況下 ,當有consumer進出consumer group時就會觸發rebalance,所謂rebalance就是重新制訂一個分區分配方案。而制訂好了分區分配方案,就得及時告知各個consumer,這就與 heartbeat.interval.ms 參數有關了。
具體來說就是:

  • 每個consumer 都會根據 heartbeat.interval.ms 參數指定的時間週期性地向group coordinator發送 hearbeat
  • group coordinator 會給各個consumer響應,若發生了 rebalance,各個consumer收到的響應中會包含 REBALANCE_IN_PROGRESS 標識,這樣各個consumer就知道已經發生了rebalance,同時 group coordinator也知道了各個consumer的存活情況。

那爲什麼要把 heartbeat.interval.mssession.timeout.ms 進行對比呢?
session.timeout.ms是指:group coordinator檢測consumer發生崩潰所需的時間。一個consumer group裏面的某個consumer掛掉了,最長需要 session.timeout.ms 秒檢測出來。
舉個示例:

session.timeout.ms=10
heartbeat.interval.ms=3

session.timeout.ms是個"邏輯"指標,它指定了一個閾值---10秒,在這個閾值內如果coordinator未收到consumer的任何消息,那coordinator就認爲consumer掛了。而heartbeat.interval.ms是個"物理"指標,它告訴consumer要每3秒給coordinator發一個心跳包,heartbeat.interval.ms越小,發的心跳包越多,它是會影響發TCP包的數量的,產生了實際的影響,這也是我爲什麼將之稱爲"物理"指標的原因。

如果group coordinator在一個heartbeat.interval.ms週期內未收到consumer的心跳,就把該consumer移出group,這有點說不過去。就好像consumer犯了一個小錯,就一棍子把它打死了。事實上,有可能網絡延時,有可能consumer出現了一次長時間GC,影響了心跳包的到達,說不定下一個heartbeat就正常了。

heartbeat.interval.ms肯定是要小於session.timeout.ms的,如果consumer group發生了rebalance,通過心跳包裏面的REBALANCE_IN_PROGRESSconsumer就能及時知道發生了rebalance,從而更新consumer可消費的分區。而如果超過了session.timeout.msgroup coordinator都認爲consumer掛了,那也當然不用把 rebalance信息告訴該consumer了。

kafka0.10.1之後的版本中,將session.timeout.msmax.poll.interval.ms 解耦了。也就是說:new KafkaConsumer對象後,在while true循環中執行consumer.poll拉取消息這個過程中,其實背後是有2個線程的,即一個kafka consumer實例包含2個線程:一個是heartbeat 線程,另一個是processing線程,processing線程可理解爲調用consumer.poll方法執行消息處理邏輯的線程,而heartbeat線程是一個後臺線程,對程序員是"隱藏不見"的。如果消息處理邏輯很複雜,比如說需要處理5min,那麼 max.poll.interval.ms可設置成比5min大一點的值。而heartbeat 線程則和上面提到的參數 heartbeat.interval.ms有關,heartbeat線程 每隔heartbeat.interval.mscoordinator發送一個心跳包,證明自己還活着。只要 heartbeat線程 在 session.timeout.ms 時間內 向 coordinator發送過心跳包,那麼 group coordinator就認爲當前的kafka consumer是活着的。

kafka0.10.1之前,發送心跳包和消息處理邏輯這2個過程是耦合在一起的,試想:如果一條消息處理時長要5min,而session.timeout.ms=3000ms,那麼等 kafka consumer處理完消息,group coordinator早就將consumer 移出group了,因爲只有一個線程,在消息處理過程中就無法向group coordinator發送心跳包,超過3000ms未發送心跳包,group coordinator就將該consumer移出group了。而將二者分開,一個processing線程負責執行消息處理邏輯,一個heartbeat線程負責發送心跳包,那麼:就算一條消息需要處理5min,只要heartbeat線程在session.timeout.ms時間內向group coordinator發送了心跳包,那consumer可以繼續處理消息,而不用擔心被移出group了。另一個好處是:如果consumer出了問題,那麼在 session.timeout.ms內就能檢測出來,而不用等到 max.poll.interval.ms 時長後才能檢測出來。

一次kafka consumer 不斷地 rebalance 分析

明白了session.timeout.msmax.poll.interval.msheartbeat.interval.ms三個參數的意義後,現在來實際分析一下項目中經常碰到的 consumer rebalance 錯誤。
一般我們是在一個線程(用戶線程)裏面執行kafka consumerwhile true循環邏輯的,其實這裏有2個線程:一個是用戶線程,另一個是心跳線程。心跳線程,我想就是根據heartbeat.interval.ms參數配置的值週期性向coordinator發送心跳包以證明consumer還活着。
如果消息處理邏輯過重,也即用戶線程需要執行很長的時間處理消息,然後再提交offset。咋一看,有一個後臺心跳線程在不斷地發送心跳啊,那爲什麼group coordinator怎麼還老是將consumer移出group,然後導致不斷地rebalance呢?
我想,問題應該是 max.poll.interval.ms這個參數引起的吧,因爲在ERROR日誌中,老是提示:消息處理邏輯花了太長的時間,要麼減少max.poll.records值,要麼增大session.timeout.ms的值。儘管有後臺heartbeat 線程,但是如果consumer的消息處理邏輯時長超過了max.poll.interval.ms ,那麼此consumer提交offset就會失敗:

org.apache.kafka.clients.consumer.CommitFailedException: 
Commit cannot be completed since the group has already rebalanced and assigned the partitions to another member.
This means that the time between subsequent calls to poll() was longer than the configured max.poll.interval.ms, 
which typically implies that the poll loop is spending too much time message processing. 
You can address this either by increasing the session timeout or by reducing the maximum size of batches returned in poll() 
with max.poll.records.

此外,在用戶線程中,一般會做一些失敗的重試處理。比如通過線程池的 ThreadPoolExecutor#afterExecute()方法捕獲到異常,再次提交Runnable任務重新訂閱kafka topic。本來消費處理需要很長的時間,如果某個consumer處理超時:消息處理邏輯的時長大於max.poll.interval.ms (或者消息處理過程中發生了異常),被coordinator移出了consumer組,這時由於失敗的重試處理,自動從線程池中拿出一個新線程作爲消費者去訂閱topic,那麼意味着有新消費者加入group,就會引發 rebalance,而可悲的是:新的消費者還是來不及處理完所有消息,又被移出group。如此循環,就發生了不停地 rebalance 的現象。

參考資料

kafka kip-62

原文:https://www.cnblogs.com/hapjin/p/10926882.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章