kafka系列——KafkaConsumer源码分析

实例化过程

与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:以上几张图是引用别的博客的,本应附上博客的地址,但是之前没保存现在一直找不到那篇博客,以后有缘遇到补上~

这部分完~

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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