02. KafkaConsumer订阅topic数据,是如何实现加入组以及再平衡原理
上一篇在分析Consumer不通订阅方式的时候,提到了同一消费组下有多个消费者时,会触发再平衡,本篇将通过源码详细的分析是如何加入组以及再平衡的实现原理。
本篇源码分析基于kafka版本:kafka-0.10.1
首先介绍一下kafka的服务端有一个GroupCoordinator组件,该组件主要负责消费者组相关信息的维护,主要有以下功能:
确定当前消费组改由哪个broker提供coordinator服务
消费组成员加入
消费组成员离开
消费组成员心跳
消费组成员已消费offset保存
消费组整体状态维护和再平衡
对GroupCoordinator组件有了大概了解之后,下面开始我们的正文
前提有如下:
topic名称:test.kafka
topic分区:2个(test.kafka-0、test.kafka-1)
消费者group.id:test.kafka_group
02.01 第一个消费者加入消费者组过程
我们创建一个KafkaConsumer消费topic数据的代码大体是如下步骤:
KafkaConsumer加入消费者组(test.kafka_group)过程都是在consumer.poll方法实现的,我们接下来参考poll方法的实现
poll方法调用了pollOnce方法
02.01.01第一步:确认coordinator是否就绪,主要作用和是服务器端的GroupCoordinator组件通信
以下是关于ensureCoordinatorReady方法的分析
向服务器发送GroupCoordinator请求,根据group.id获取给该组提供coordinator服务的broker
第一步:获取请求数量最小的node(包括broker的ip、port)
第二步:向第一步获取的broker发送GroupCoordinator请求,参数为group.id
第三步:处理服务器响应结果
第一步:获取服务器返回的为当前gourp.id提供coordinator服务的broker(服务器是通过group.id的hashcode % __consumer_offsets topic的partition 数量,该partition
leader所在服务器为返回的broker),并且初始化coordinator变量
第二步:和broker建立连接
02.01.02第二步:主要是加入消费者组,以及同步分配给当前消费者的topic partitions
以下是关于ensurePartitionAssignment方法的分析
ensurePartitionAssignment方法调用了ensureActiveGroup方法
向服务器发送joinGroup请求
第一步:创建JoinGroupRequest请求,重点介绍以下几个参数:
String groupId:group.id 参数
String memberId:消费组的成员ID,此时值为空
String protocolType:固定值”consumer”
ListgroupProtocols
通过以上可以分析出参数groupProtocols的值
第二步:通过JoinGroupResponseHandler对服务器返回结果进行处理
joinResponse返回参数介绍
joinResponse.memberId:服务器给当前消费组生成的ID
joinResponse.generationId:服务器生成的一个自增ID
joinResponse.groupProtocol:客户端如果不做任何配置,默认值是”range”
joinResponse. leaderId:如果是第一个加入消费组,leaderId和当前memberId相同
joinResponse.
members:当前消费组中所有的消费成员,只有leader会有这个参数,非leader消费者该值为空
第三步:调用onJoinLeader方法,根据members的数量和订阅的topic:test.kafka的partition数采用rang的分配策略进行分配(调用的方式是performAssignment,因为当前只有一个消费成员,所以该成员分配到两个partition),然后把分配的结果通过SyncGroupRequest请求发送给服务器
第四步:对SyncGroupRequest请求返回结果的处理
该行最后会调用ConsumerCoordinator.onJoinComplete方法如下图
把给该消费者分配的top partition设置给当前消费者,调用该消费这订阅topic时设定的再平衡监听器(此处正好回答了第一篇的问题)
到此,消费者组的第一个消费者加入组的过程介绍完成了
02.02 第二个消费者加入消费者组过程
当第二个消费者加入消费组的时候,第一个消费者会跟着联动,最终实现每一个消费者成员消费topic:test.kafka的一个partition,具体实现流程如下分析
在kafka的服务器端通过GroupMeta维护者消费者组的状态,当我们第二个消费者在没有启动之前,GroupMeta的状态处于Stable,members只有一个成员。
当第二个消费者启动时同样第一步发送GroupCoordinatorRequest(分析过程见上图)获取GroupCoordinator所在的broker,第二步发送JoinGroupRequest(分析过程见上图)加入消费组,此时会将服务器GroupMeta的状态由Stable变成PreparingRebalance再变为AwaitingSync,第二个消费者接收到了服务器生成的memberId和Leader memberId信息,由于它本身不是leader进入onJoinFollower的处理如下图
在onJoinFollower方法中,接着发送SyncGroupRequest等待从服务器获取给自己分配的topic partition(等待消费组中的leader完成重新的分配)
那么时如何触发leader重新再分配的呢?接着看下面的分析
每个KafkaConsumer和服务器一直都维护着心跳,正式通过心跳感知到了服务器GroupMeta状态的变化,然后触发leader的重新分配(源码如下)
KafkaConsumer在通过poll方法拉取数据过程会通过延时任务的方式(HeartbeatTask实现了延时接口DelayedTask)触发发送心跳包如下图
Kafka服务器收到心跳包之后由于该GroupMeta的状态是AwaitingSync,会向客户端返回Errors.REBALANCE_IN_PROGRESS的错误码,然后客户端通过重置rejoinNeeded=true,触发leader 重新发送JoinGroupRequest请求完成重新分发,然后把分配结果发送SyncGroupRequest发送给kafka服务器,kafka服务器把GroupMeta的状态由AwaitingSync变成Stable,此时非leader的第二个消费组通过SyncGroupRequest获取给自己分配需要消费的topic partition,这样就完成了再平衡回复到正常消费状态