實例化過程
與KafkaProduer類似,只是初始化的組件有所差異,看KafkaConsumer構造函數
消費者實例化的主要組件介紹
ConsumerConfig:消費者級別的配置,將相應配置傳遞給其他組件
ConsumerCoodinator:負責消費者與服務端 GroupCoordinator 通信
ConsumerNetworkClient:對網絡層通信 NetworkClient 的封裝,用於消費者與服務端的通信
Fetcher:對 ConsumerNetworkClient 進行了包裝,負責從服務端獲取消息
SubscriptionState:維護了消費者訂閱和消費消息的情況,保存訂閱信息的字段,其中主要屬性如下,
- Set<String> subscription:用來保存客戶端通過KafkaConsumer.subscribe()方法所訂閱的主題列表
- Pattern subscribedPattern:用來保存通過模式匹配訂閱主題的模式
- Set<String> groupSubscription:用來保存該消費者當前訂閱的主題列表
- PartitionStates<TopicPartitionState> assignment:用來保存消費者對所訂閱的每個主題分區的消費情況與客戶端通過 KafkaConsumer.assign()方法所訂閱的分區列表
- TopicPartitionState內部類,該類定義了某個消費者對某個TopicPartiton 的消費情況
消費者訂閱topic的方式
2種訂閱topic的方式:
1種是通過subscribe方法指定消息對應的主題,支持以正則表達式方式指定主題
1種是assign方法指定需要消費的分區
ps:使用正在表達式方式,會定期檢測topic,當topic數量或topic分區數量發生變化時,會將該topic在消費者組訂閱的topic列表種刪除,然後進行再平衡操作;需要注意的是,這兩種訂閱topic的方式是互斥的,在客戶端只能選擇其中一種
消費者拉取消息
KafkaComsumer的poll方法用於從服務器拉取消息,可指定等待時長timeout,若timeout=0,則在沒有拉取到消息時無需等待重試直接返回客戶端;否則在timeout時間內進行重試直到取到消息or在等待超過timeout時間後構造響應結果返回客戶端;真正拉取消息是通過Fetcher類構造拉取消息的FetchRequest 請求,然後通過ConsumerNetworkClient發送FetchRequest請求,最後對返回的結果進行處理並更新緩存中記錄的消費位置
Fetcher類定義核心字段的介紹
- ConsumerNetworkClient client:用於向kafka節點發送網絡請求,其中定義了UnsentRequests unsent字段,起緩衝隊列的作用,保存每個節點與發送到該節點的請求列表(ConcurrentMap<Node, ConcurrentLinkedQueue<ClientRequest>>)
- Metadata metadata:維護和管理 Kafka 集羣的元數據信息
- SubscriptionState subscriptions:維護了當前消費者訂閱和消費消息的情況,保存訂閱信息
- ConcurrentLinkedQueue<CompletedFetch> completedFetches:用於保存kafka響應的FetchResponse的原始結果的隊列
- PartitionRecords nextInLineRecords:對CompletedFetch解析之後的結果的封裝類,保存從CompletedFetch解析後的消息
Fetcher如何拉取消息(sendFetcher方法的邏輯)
2種確定消費起始位置的方式
a.Api方式:
- seek方法:指定消費起始位置到一個特定位置
- seekToBeginning方法:指定OffsetResetStrategy爲”EARLIEST”,相當於通過配置項auto.offset.reset設置消費偏移量重置策略爲 earliest的方式
- seekToEnd方法:設置OffsetResetStrategy爲”LATEST”,相當於通過配置項auto.offset.reset設置消費偏移量重置策略爲latest 的方式
https://blog.csdn.net/lishuangzhe7047/article/details/74530417
b.自動設置消費起始位置:
通過auto.offset.reset配置項設置消費起始位置,默認值爲LATEST;在 KafkaConsumer 初始化時會讀取配置項 auto.offset.reset 配置的消費位置重置策略初始化SubscriptionState;在 pollOnce()方法在執行時會檢測是否訂閱的主題和分區都已設置了消費起始位置,即訂閱列表對應的Topic PartitionState.position不爲空,若訂閱列表中存在TopicPartitonState.position爲空,則先通過Fetcher 根據自動重置策略獲取消費起始位置,若仍有部分訂閱分區沒有獲取到消費起始位置,則通過 Fetcher 向 Kafka 集羣發送 OffsetFetchRequest 請求,請求獲取消費起始位置
消費者偏移量的保存
舊版消費者保存在zookeeper的/consumers/${group.id}/offsets/${topicName}/${partitionId}節點中
新版消費者保存到Kafka一個內部主題”__consumer_offsets”中,該topic總保留各分區被消費的最新偏移量,消費偏移量如同普通消息一樣追加到該主題相應的分區當中,根據算法(Math.abs(${group.id}.hashCode()%${offsets.topic.num.partitions}來確定消費偏移量提交的分區(offsets.topic.num.partitions指的是__consumer_offsets這個topic的分區數,可配置,默認50)
格式爲:key=group.id,topic,partition value=offset,metadata,timestamp
消費者偏移量提交的2種方式
a.api手動提交方式(enable.auto.commit需置爲false):
commitSync方法:同步提交
commitAsync()方法:異步提交
都是通過ConsumerCoordinator發送偏移量消息追加到Kafka內部主題當中
同步與異步的區別:同步提交時,KafkaConsumer 在提交請求響應結果返回前會一直被阻塞,在成功提交後纔會進行下一次拉取消息操作;異步提交時KafkaConsumer不會被阻塞,但當提交發生異常時就有可能發生重複消費的問題,但異步方式會提高消費吞吐量(如:提交不成功時候消費者線程出異常掛了,下一次消費者起來便會重複消費數據)
b.自動提交方式(enable.auto.commit需置爲true): 可通過配置項auto.commit.interval.ms來設置提交操作的時間間隔,自動提交併非通過定時任務週期性地提交,而是在一些特定事件發生時才檢測與上一次提交的時間間隔是否超過了${auto.commit.interval.ms}計算出的下一次提交的截止時間nextAutoCommitDeadline,若時間間隔超過了nextAutoCommitDeadline 則請求提交偏移量,同時更新下一次提交消費偏移量的nextAutoCommitDeadline
特定事件如下:
- a.通過 KafkaConsumer.assign()訂閱分區
- b.ConsumerCoordinator.poll()方法處理時(maybeAutoCommitOffsetsAsync方法)
- c.在消費者進行平衡操作前
- d.ConsumerCoordinator 關閉操作
分區數與消費者線程的關係
可通過配置項partition.assignment.strategy來設置消費者線程與分區映射關係,有如下2種配置
range 分配策略:默認值,按照線程總數與分區總數進行整除運算計算一個跨度,然後將分區按跨度進行平均分配,以保證分區儘可能均衡地分配給所有消費者線程(對應RangeAssignor類),舉例如下:
round-robin 分配策略:將訂閱的主題分區以及消費者線程進行排序,然後通過輪詢方式逐個將分區依次分給消費者線程(對應RoundRobinAssignor類),舉例如下:
range策略總結:
Pnt 表示分區總數,Cnt 表示消費者線程總數:
- 若Pnt > Cnt,則有部分消費者線程會分配到多個分區,從而部分消費者線程會收到多個分區消息,這種情況下若對消息順序有要求的場景,則要實現相應機制來保證消息的順序 ·
- 若 Pnt = Cnt,則每個消費者線程分配到一個分區,每個消費者收到固定分區的消息
- 若Pnt < Cnt,則有部分消費者線程分配不到分區,這導致分配不到分區的消費者線程將收不到任何消息
range策略訂閱多個主題分配過程與單個主題類似,多個主題分配拆分爲多次單個主題分配即可,舉例如下:
發生消費者平衡過程的情況:
a.新的消費者加入消費組
b.當前消費者從消費組退出(這裏的退出包括異常退出和消費者正常關閉 )
c.消費者取消對某個主題的訂閱
d.訂閱主題的分區增加
e.代理宕機新的協調器當選
f.當消費者在${session.timeout.ms}毫秒內還沒發送心跳請求,組協調器認爲消費者已退出
Coordinator
作用:用於對consumer group進行的管理,管理消費者偏移量、執行rebalance
老版本coordinator(0.9以前):依賴zookeeper來發揮作用,監聽zookeeper的節點變化來管理偏移量與執行rebalance
新版本coordinator(0.9開始):不依賴zookeeper,爲每個consumer group分配一個coordinator,consumer group內的成員與該coordinator進行協調通信來完成偏移量管理與rebalance
consumer group如何確定coordinator:
a.確定位移信息寫入__consumers_offsets的哪個分區,公式如下:(Math.abs(${group.id}.hashCode()%${offsets.topic.num.partitions}來確定消費偏移量提交的分區(offsets.topic.num.partitions指的是__consumer_offsets這個topic的分區數,可配置,默認50)
b.該分區leader所在的broker就是被選定的coordinator
rebalance概念介紹:
Rebalance Generation:相當於JVM GC的分代,表示了rebalance之後的一屆成員,用於保護consumer group,隔離無效的offset提交(如:上一屆的consumer成員是無法提交位移到新一屆的consumer group中),每次group進行rebalance之後,generation號都會加1,表示group進入到了一個新的版本
rebalance協議:
- Heartbeat請求:consumer需定期給coordinator發送心跳來表明自己還活着,超過了設定的超時時間則認爲其掛了
- LeaveGroup請求:主動告訴coordinator我要離開consumer group
- SyncGroup請求:group leader把分配方案告訴組內所有成員
- JoinGroup請求:成員請求加入組
- DescribeGroup請求:顯示組的所有信息,包括成員信息,協議名稱,分配方案,訂閱信息等,通常該請求是給管理員使用
實踐topic分區offset與__consumer_offsets的關係
1.創建一個topic爲mytest
2.創建消費者訂閱mytest 的topic,所屬consumer group爲test-kafka-offset
3.啓動消費者消費數據(由於只有1個消費者,故同時消費4分區),消費者每5s會提交一次offset即使沒數據消費 kafka-console-consumer.sh --bootstrap-server kafka-ip:port --group test-kafka-offset --topic mytest
4.計算該consumer group的offset在__consumer_offsets中的分區
5.Math.abs("test-kafka-offset".hashCode()) % 50=15
6.起個消費者訂閱__consumer_offsets,可以看到該test-kafka-offset裏面consumer的消費情況如下
查看命令:
kafka-console-consumer.sh --topic __consumer_offsets --partition 15 --bootstrap-server kafka-ip:port --formatter "kafka.coordinator.group.GroupMetadataManager\$OffsetsMessageFormatter"
7.創建一個生產者往mytest中塞4條數據
kafka-console-producer.sh --broker-list kafka-ip:port --topic mytest
8.再查看__consumer_offsets中test-kafka-offset的消費情況如下
9.不斷生產數據會發現偏移量不斷髮生變化
圖示Rebalance場景
1.新成員加入consumer group:
2.consumer group成員崩潰:
3.consumer group成員主動離組:
4.提交消費偏移量:
ps:以上幾張圖是引用別的博客的,本應附上博客的地址,但是之前沒保存現在一直找不到那篇博客,以後有緣遇到補上~
這部分完~