kafka裏的offset管理和Consumer Rebalance

個人博客請訪問 http://www.x0100.top                 

1、Consumer與Consumer Group

consumer group是kafka提供的可擴展且具有容錯性的消費者機制。組內可以有多個消費者或消費者實例(consumer instance),它們共享一個公共的ID,即group ID。組內的所有消費者協調在一起來消費訂閱主題(subscribed topics)的所有分區(partition)。
consumer group下可以有一個或多個consumer instance,consumer instance可以是一個進程,也可以是一個線程
group.id是一個字符串,唯一標識一個consumer group
consumer group下訂閱的topic下的每個分區只能分配給某個group下的一個consumer(當然該分區還可以被分配給其他group)

2、Coordinator介紹

Coordinator一般指的是運行在broker上的group Coordinator,用於管理Consumer Group中各個成員,每個KafkaServer都有一個GroupCoordinator實例,管理多個消費者組,主要用於offset位移管理和Consumer Rebalance。

對於每個Consumer Group,Coordinator會存儲以下信息:

  1. 對每個存在的topic,可以有多個消費組group訂閱同一個topic(對應消息系統中的廣播)

  2. 對每個Consumer Group,元數據如下:
    訂閱的topics列表
    Consumer Group配置信息,包括session timeout等
    組中每個Consumer的元數據。包括主機名,consumer id
    每個正在消費的topic partition的當前offsets
    Partition的ownership元數據,包括consumer消費的partitions映射關係

 

consumer group如何確定自己的coordinator是誰呢? 簡單來說分爲兩步:

  • 確定consumer group位移信息寫入__consumers_offsets這個topic的哪個分區。具體計算公式:
    __consumers_offsets partition# = Math.abs(groupId.hashCode() % groupMetadataTopicPartitionCount) 注意:groupMetadataTopicPartitionCount由offsets.topic.num.partitions指定,默認是50個分區。

 

  • 該分區leader所在的broker就是被選定的coordinator

3、offset位移管理

消費者在消費的過程中需要記錄自己消費了多少數據,即消費位置信息。在Kafka中這個位置信息有個專門的術語:位移(offset)。
(1)、很多消息引擎都把這部分信息保存在服務器端(broker端)。這樣做的好處當然是實現簡單,但會有三個主要的問題:
1. broker從此變成有狀態的,會影響伸縮性;
2. 需要引入應答機制(acknowledgement)來確認消費成功。
3. 由於要保存很多consumer的offset信息,必然引入複雜的數據結構,造成資源浪費。
而Kafka選擇了不同的方式:每個consumer group管理自己的位移信息,那麼只需要簡單的一個整數表示位置就夠了;同時可以引入checkpoint機制定期持久化,簡化了應答機制的實現。
(2)、Kafka默認是定期幫你自動提交位移的(enable.auto.commit = true),你當然可以選擇手動提交位移實現自己控制。
(3)、另外kafka會定期把group消費情況保存起來,做成一個offset map,如下圖所示:

圖中表明瞭test-group這個組當前的消費情況

老版本的位移是提交到zookeeper中的,目錄結構是:/consumers/<group.id>/offsets/<topic>/<partitionId>,但是zookeeper其實並不適合進行大批量的讀寫操作,尤其是寫操作。

因此kafka提供了另一種解決方案:增加__consumeroffsets topic,將offset信息寫入這個topic,擺脫對zookeeper的依賴(指保存offset這件事情)。__consumer_offsets中的消息保存了每個consumer group某一時刻提交的offset信息。依然以上圖中的consumer group爲例,格式大概如下:

 

 

__consumers_offsets topic配置了compact策略,使得它總是能夠保存最新的位移信息,既控制了該topic總體的日誌容量,也能實現保存最新offset的目的。

offset提交消息會根據消費組的key(消費組名稱)進行分區. 對於一個給定的消費組,它的所有消息都會發送到唯一的broker(即Coordinator)
Coordinator上負責管理offset的組件是Offset manager。負責存儲,抓取,和維護消費者的offsets. 每個broker都有一個offset manager實例. 有兩種具體的實現:
ZookeeperOffsetManager: 調用zookeeper來存儲和接收offset(老版本的位移管理)。
DefaultOffsetManager: 提供消費者offsets內置的offset管理。
通過在config/server.properties中的offset.storage參數選擇。
DefaultOffsetManager
除了將offset作爲logs保存到磁盤上,DefaultOffsetManager維護了一張能快速服務於offset抓取請求的consumer offsets表。這個表作爲緩存,包含的含僅僅是”offsets topic”的partitions中屬於leader partition對應的條目(存儲的是offset)。
對於DefaultOffsetManager還有兩個其他屬性: “offsets.topic.replication.factor和”offsets.topic.num.partitions”,默認值都是1。這兩個屬性會用來自動地創建”offsets topic”。
offset manager接口的概要:

Offset Commit提交過程:
消費端
一條offset提交消息會作爲生產請求.當消費者啓動時,會爲”offsets topic”創建一個消費者. 下面是內置的生產者的一些屬性:
可以使用異步.但是使用同步可以避免延遲的生產請求(因爲是批量消息),並且我們需要立即知道offset消息是否被broker成功接收
|request.required.acks|-1|確保所有的replicas和leader是同步的,並且能看到所有的offset消息
|key.serializer.class|StringEncoder|key和payload都是strings
注意我們沒有對提交的offset消息進行壓縮,因爲每條消息本身大小是很小的,如果壓縮了反而適得其反.
目前key和offset的值通過純文本方式傳遞. 我們可以轉換爲更加緊湊的二進制協議,而不是把
Long類型的offset和Int類型的partition作爲字符串. 當然在不斷演進時還要考慮版本和格式協議.
broker端
broker把接收到的offset提交信息當做一個正常的生產請求,對offset請求的處理和正常的生產者請求處理方式是一樣的.
一旦將數據追加到leader的本地日誌中,並且所有的replicas都趕上leader.leader檢查生產請求是”offsets topic”,
(因爲broker端的處理邏輯針對offset請求和普通生產請求是一樣的,如果是offset請求,還需要有不同的處理分支)
它就會要求offset manager添加這個offset(對於延遲的生產請求,更新操作會在延遲的生產請求被完成的時候).
因爲設置了acks=-1,只有當這些offsets成功地複製到ISR中的所有brokers,纔會被提交給offset manager.

3、Consumer Reblance

什麼是rebalance?
rebalance本質上是一種協議,規定了一個consumer group下的所有consumer如何達成一致來分配訂閱topic的每個分區。比如某個group下有20個consumer,它訂閱了一個具有100個分區的topic。正常情況下,Kafka平均會爲每個consumer分配5個分區。這個分配的過程就叫rebalance。Kafka新版本consumer默認提供了兩種分配策略:range和round-robin。
rebalance的觸發條件有三種:
組成員發生變更(新consumer加入組、已有consumer主動離開組或已有consumer崩潰了——這兩者的區別後面會談到)
訂閱主題數發生變更——這當然是可能的,如果你使用了正則表達式的方式進行訂閱,那麼新建匹配正則表達式的topic就會觸發rebalance
訂閱主題的分區數發生變更

Rebalance Generation
JVM GC的分代收集就是這個詞(嚴格來說是generational),我這裏把它翻譯成“屆”好了,它表示了rebalance之後的一屆成員,主要是用於保護consumer group,隔離無效offset提交的。比如上一屆的consumer成員是無法提交位移到新一屆的consumer group中。我們有時候可以看到ILLEGAL_GENERATION的錯誤,就是kafka在抱怨這件事情。每次group進行rebalance之後,generation號都會加1,表示group進入到了一個新的版本,如下圖所示:Generation 1時group有3個成員,隨後成員2退出組,coordinator觸發rebalance,consumer group進入Generation 2,之後成員4加入,再次觸發rebalance,group進入Generation 3.

協議(protocol)
rebalance本質上是一組協議。group與coordinator共同使用它來完成group的rebalance。目前kafka提供了5個協議來處理與consumer group coordination相關的問題:
Heartbeat請求:consumer需要定期給coordinator發送心跳來表明自己還活着
LeaveGroup請求:主動告訴coordinator我要離開consumer group
SyncGroup請求:group leader把分配方案告訴組內所有成員
JoinGroup請求:成員請求加入組
DescribeGroup請求:顯示組的所有信息,包括成員信息,協議名稱,分配方案,訂閱信息等。通常該請求是給管理員使用
Coordinator在rebalance的時候主要用到了前面4種請求。
liveness
consumer如何向coordinator證明自己還活着?通過定時向coordinator發送Heartbeat請求。如果超過了設定的超時時間,那麼coordinator就認爲這個consumer已經掛了。一旦coordinator認爲某個consumer掛了,那麼它就會開啓新一輪rebalance,並且在當前其他consumer的心跳response中添加“REBALANCE_IN_PROGRESS”,告訴其他consumer:不好意思各位,你們重新申請加入組吧!
Rebalance過程
rebalance的前提是coordinator已經確定了。
總體而言,rebalance分爲2步:Join和Sync
Join, 顧名思義就是加入組。這一步中,所有成員都向coordinator發送JoinGroup請求,請求入組。一旦所有成員都發送了JoinGroup請求,coordinator會從中選擇一個consumer擔任leader的角色,並把組成員信息以及訂閱信息發給leader——注意leader和coordinator不是一個概念。leader負責消費分配方案的制定。

Sync,這一步leader開始分配消費方案,即哪個consumer負責消費哪些topic的哪些partition。一旦完成分配,leader會將這個方案封裝進SyncGroup請求中發給coordinator,非leader也會發SyncGroup請求,只是內容爲空。coordinator接收到分配方案之後會把方案塞進SyncGroup的response中發給各個consumer。這樣組內的所有成員就都知道自己應該消費哪些分區了。

注意!consumer group的分區分配方案是在客戶端執行的!Kafka將這個權利下放給客戶端主要是因爲這樣做可以有更好的靈活性。比如這種機制下我可以實現類似於Hadoop那樣的機架感知(rack-aware)分配方案,即爲consumer挑選同一個機架下的分區數據,減少網絡傳輸的開銷。Kafka默認爲你提供了兩種分配策略:range和round-robin。由於這不是本文的重點,這裏就不再詳細展開了,你只需要記住你可以覆蓋consumer的參數:partition.assignment.strategy來實現自己分配策略就好了。

和很多kafka組件一樣,group也做了個狀態機來表明組狀態的流轉。coordinator根據這個狀態機會對consumer group做不同的處理,如下圖所示

簡單說明下圖中的各個狀態:
Dead:組內已經沒有任何成員的最終狀態,組的元數據也已經被coordinator移除了。這種狀態響應各種請求都是一個response:UNKNOWN_MEMBER_ID
Empty:組內無成員,但是位移信息還沒有過期。這種狀態只能響應JoinGroup請求
PreparingRebalance:組準備開啓新的rebalance,等待成員加入
AwaitingSync:正在等待leader consumer將分配方案傳給各個成員
Stable:rebalance完成!可以開始消費了
rebalance場景剖析
1 新成員加入組(member join)

2 組成員崩潰(member failure)
組成員崩潰和組成員主動離開是兩個不同的場景。因爲在崩潰時成員並不會主動地告知coordinator此事,coordinator有可能需要一個完整的session.timeout週期(心跳週期)才能檢測到這種崩潰,這必然會造成consumer的滯後。可以說離開組是主動地發起rebalance;而崩潰則是被動地發起rebalance。如圖:

3 組成員主動離組(member leave group)

4 提交位移(member commit offset)

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