KAFKA-consumer rebalance
consumer.subscribe(Arrays.asList(topic), new ConsumerRebalanceListener()
consumer可以实现rebalance监听器,监听记录rebalance
第一阶段 找GroupCoordinitor
消费者需要确定它所属的消费组对应的 GroupCoordinator 所在的 broker, 并创建与该 broker
相互通信的网络连接 。 如果消费者己经保存了与消费组对应的 GroupCoordinator 节点的信息,
并且与它之间的 网络连接是正常的,那么就可以进入第二阶段。否则, 就需要向集群中 的某个
节点发送 FindCoordinatorRequest 请求来查找对应的 GroupCoordinator , 这里的“某个节点 ” 并
非是集群中的任意节点,而是负载最小的节点,即 2.2.2 节中的 leastLoadedNode 。
FindCoordinatorRequest 请求体中只有两个域(Field ) : coordinator key
和 coordinator_type。coordinator_key 在这里就是消费组的名称,即 groupid ,
coordinator type 置为 0。
Kafka 在收到 FindCoordinatorRequest 请求之后,会根据 coordinator_key (也就是
groupld )查找对应的 GroupCoordinator 节点,如果找到对应的 GroupCoordinator 则会返回其相
对应的 node_id、 host 和 port 信息。
具体查找 GroupCoordinator 的方式是先根据消费组 groupid 的哈希值计算__consumer_offsets
中的分区编号,具体算法如代码清单 7 - 1 所示。
Utils.abs(groupid.hashCode) % groupMetadataTopicPartitionCount
其中 groupid.hashCode 就是使用 Java 中 String 类的 hashCode()方法获得的,
groupMetadataTopicPartitionCount 为主题 __consumer_offsets 的分区个数,这个可以
通过 broker 端参数 offsets.topic.num.partitions 来配置,默认值为 50 。
找到对应的 consumer offsets 中的分区之后,再寻找此分区 leader 副本所在的 broker 节点,
该 broker 节点即为这个 groupld 所对应的 GroupCoordinator 节点。消费者 groupId 最终的分区分
配方案及组内消费者所提交的消费位移信息都会发送给此分区 leader 副本所在的 broker 节点 ,
让此 broker 节点既扮演 GroupCoordinator 的角色,又扮演保存分区分配方案和组内消费者位移
的角色,这样可以省去很多不必要的中间轮转所带来的开销。
第二阶段( JOIN GROUP )
在成功找到消费组所对应的 GroupCoordinator 之后就进入加入消费组的阶段,在此阶段的
消费者会向 GroupCoordinator 发送 JoinGroupRequest 请求,并处理响应。
消费者在发送 JoinGroupRequest 请求之后会阻塞等待 Kafka 服务端的响应。服务端在收到
JainGroupRequest 请求后会交由 GroupCoordinator 来进行处理 。GroupCoordinator 首先会对
JoinGroupRequest 请求做合法性校验,比如 groupid 是否为空、当前 broker 节点是否是请求
的消费者组所对应的组协调器、 rebalance timeout 的值是否在合理的范围之内。如果消费
者是第一次请求加入消费组,那么 JoinGroupRequest 请求中的 member_id 值为 null,即没有
它自身的唯一标志,此时组协调器负责为此消费者生成一个 member id。这个生成的算法很
简单,具体如以下伪代码所示。
String memberid = clientid + "-" + UUID.randomUUID().toString() ;
其中 clientld 为消费者客户端的 clientld,对应请求头中的 client id。由此可见消费者的
memberd 由 clientId 和 UUID 用"-" 字符拼接而成。
选举消费纽的 leader
GroupCoordinator 需要为消费组内的消费者选举出一个消费组的 leader ,这个选举的算法也
很简单,分两种情况分析。如果消费组内还没有 leader,那么第一个加入消费组的消费者即为
消费组的 leader。如果某一时刻 leader 消费者由于某些原因退出了消费组,1 那么会重新选举一
个新的 leader,这个重新选举 leader 的过程又更“随意”了,相关代码如下 :
解释一下这 2 行代码:在 GroupCoordinator 中消费者的信息是以 HashMap 的形式存储的,
其中 key 为消 费者的 member id,而 value 是消费者相关的元数据信息。 leaderld 表示 leader
消费者的 member id,它的取值为 HashMap 中的第一个键值对的 key,这种选举的方式基本
上和随机无异 。 总体上来说,消费组的 leader 选举过程是很随意的。
选举分区分配策略
每个消费者都可以设置自己的分区分配策略,对消费组而言需要从各个消费者呈报上来的
各个分配策略中选举一个彼此都“信服”的策略来进行整体上的分区分配 。 这个分区分配的选
举并非由 leader 消费者决定,而是根据消费组内的各个消费者投票来决定的。这里所说的 “根
据组内的各个消费者投票来决定”不是指 GroupCoordinator 还要再与各个消费者进行进一步交
互,而是根据各个消费者呈报的分配策略来实施。最终选举的分配策略基本上可以看作被各个
消费者支持的最多的策略,具体的选举过程如下:
(1)收集各个消费者支持的所有分配策略,组成候选集 candidates 。
(2)每个消费者从候选集 candidates 中 找出第一个自身支持的策略,为这个策略投上一票。
(3)计算候选集中各个策略的选票数,选票数最多的策略即为当前消费组的分配策略。
如果有消费者并不支持选出的分配策略,那么就会报出异常 IllegalArgumentException:
Member does not support protocol。 需要注意的是,这里所说的“消费者所支持的分配策略”是
指 partition.assignment.strategy 参数配置的策略,如果这个参数值只配置了
RangeAssignor , 那么这个消费者客户端只支持 RangeAssignor 分配策略,而不是消费者客户端
代码中实现的 3 种分配策略及可能的自定义分配策略 。
第三阶段 (SYNC GROUP)
leader 消费者根据在第二阶段中选举出来的分区分配策略来实施具体的分区分配,在此之
后需要将分配的方案同步给各个消费者,此时 leader 消费者并不是直接和其余的普通消费者同
步分配方案,而是通过 GroupCoordinator 这个“中间人”来负责转发同步分配方案的 。 在第三
阶段,也就是同步阶段,各个消费者会向 GroupCoordinator 发送 SyncGroupRequest 请求来同步
分配方案,
我们再来看一下SyncGroupRequest 请求的具体结构 ,如 图 7-12 所示 。 SyncGroupRequest
中的 group id 、 generation id 和 member id 前面都有涉及,这里不再赘述 。 只有 leader
消费者发送的 SyncGroupRequest 请求中才包含具体的分区分配方案,这个分配方案保存在
group assignment 中 ,而其余消费者发送的 SyncGroupRequest 请求中的 group assignment
为空。
与 JoinGroupRequest 请求中的 protocol metadata 类似,都可以细分为 3 个更具体的
字段,只不过 protocol metadata 存储的是主题的列表信息,而 member assignment 存
储的是分区信息, member assignment 中可以包含多个主题的多个分区信息。
服务端在收到消费者发送的 SyncGroupRequest 请求之后会交由 GroupCoordinator 来负责具
体的逻辑处理。 GroupCoordinator 同样会先对 SyncGroupRequest 请求做合法性校验,在此之后
会将从 leader 消费者发送过来的分配方案提取出来,连同整个消费组的元数据信息一起存入
Kafka 的 consumer offsets 主题中 , 最后发送响应给各个消费者 以提供给各个消费者各自所属
的分配方案。