Coordinator与offset管理和Consumer Rebalance
消费者组
消费者组(consumer group)是Kafka提供的可扩展且具有容错性的消费者机制
主要有三个特性:
- consumer group下可以有一个或多个消费者,或是消费者实例(consumer instance),consumer instance可以是一个进程,也可以是一个线程
- group.id:是一个字符串,消费者组的唯一标识
- 一个group内部,1个parition只能被1个consumer拥有,也可以分配给其他Group的consumer
为什么在一个group内部,1个parition只能被1个consumer拥有
在一个Group内部,是不允许多个consumer消费同一个partition,所以对于一个topic,一个group,其中partition的个数 >= consumer的个数。
例如:对于一个topic,有4个partition,那么在一个group内部,最多只能有4个consumer,当再加入新的consumer时,也不会分配到partition
- 一个partition只能被一个group中的一个consumer消费,是为了保证1个partition消息的时序
- 每个topic的每个partition的每个consumer group对应1个offset,即结构为键值对类型:(topic,partition,consumer_group_id)-offset,如果一个group内多个消费者消费同一个partition,那么offset就会出现问题
位移管理
参考Kafka如何保证消息不丢失不重复 中****消费端确保实现精确一次消费**
老版本中位移提交是提交到了zookeeper中,目录结构为:
/consumers/<group.id>/offsets/<topic>/<partitionId>
新版offset保存位置
提交到zookeeper有一定缺陷,它并不适合进行大批量的读写操作,尤其是写操作
Kafka提供了另一种解决方案:增加_consumer_offsets 这个topic,将offset信息写入这个topic,减少对zookeeper的依赖。其中_consumer_offsets里面记录的内容有:group.id,topic,partition,offset
每个group保存在_consumer_offsets的位置
Kafka会使用下面公式计算该group位移保存在__consumer_offsets的哪个分区上:
Math.abs(groupID.hashCode()) % numPartitions
//numPartitions 由offsets.topic.num.partitions指定,默认是50个分区
对应的分区=Math.abs(“console-consumer-46965”.hashCode()) % 50 = 11,即__consumer_offsets的分区11保存了这个consumer group的位移信息
Rebalance
什么是rebalance
rebalance本质上是一种协议,规定了一个consumer group下的所有consumer如何达成一致来分配订阅topic的每个分区。比如某个group下有20个consumer,它订阅了一个具有100个分区的topic。正常情况下,Kafka平均会为每个consumer分配5个分区。这个分配的过程就叫rebalance
什么时候发生rebalance
- 组成员变更(新consumer加入组,已有consumer主动离开组或已有consumer崩溃)
- 订阅主题数发生变更(使用正则表达式进行订阅,则新建topic匹配上则会发生rebalance)
- 订阅主题的分区数发生变更
如何进行组内分区分配
- Kafka新版本consumer默认提供两种分配策略:range和round-robin,consumer group会默认帮我们把订阅topic的分区进行分配
- Kafka可采用可插拔式分配策略,可以创建自己的分配器实现不同的分配策略
coordinator
Kafka提供一个角色:coordinator来执行rebalance和consumer group的管理
不同版本的coordinator
-
0.8版本的coordinator:
- coordinator依赖zookeeper来实现管理。coordinator监听zookeeper的/consumers/《group》/ids的子节点变化以及/brokers/topics/《topic》数据变化判断是否需要进行rebalance
- group下的每个consumer自己决定要消费哪些分区,并把自己决定抢先在zookeeper中的/consumers/《group》/owners/《topic》/《partition》下注册,需要zookeeper的协助才可以
-
0.9版本的coordinator
- 每个consumer group都被分配一个coordinator用于组管理和位移管理
- 这时coordinator承担更多责任,比如:组成员管理、位移提交保护机制
- group内成员都会和coordinator进行协商通信,对zookeeper依赖减小,性能得到提升
如何确定coordinator
在consumer group中如何确定谁是coordinator,简单分为两步:
-
确定consumer group位移信息写入_consumers_offsets的哪个分区
- 计算公式:partition = Math.abs(groupId.hashCode() %groupMetadataTopicPartitionCount)
- groupMetadataTopicPartitionCount由offsets.topic.num.partitions指定,默认是50个分区
-
该分区Leader所在的broker就是被选定的coordinator
Rebalance Generation
consumer group 每次经过rebalance之后会变为新一届的成员,generation号都会加1,表示进入一个新版本
主要是为了保护consumer group,隔离无效的offset提交。比如:上一届的consumer成员无法提交位移到新一届的consumer group中,否则可能会报ILLEGAL_GENERATION错误
协议(protocol)
rebalance本质上是一组协议。group与coordinator共同使用它来完成group的rebalance。目前kafka提供了5个协议来处理与consumer group、 coordination相关的问题,Coordinator在rebalance的时候主要用到了前面4种请求:
- HeartBeat请求:consumer需要定期给coordinator发送心跳表明自己还活着
- LeaveGroup请求:主动告诉coordinator要离开consumer group
- SyncGroup请求:group leader把分配方案告诉组内其他所有成员
- JoinGroup请求:成员请求加入组
- DescribeGroup请求:显示组的所有信息,包括成员信息、协议名称、分配方案、订阅信息等,通常该请求给管理员使用
liveness
consumer需要定时向coordinator发送HeartBeat请求。如果超过了设定时间没收到,coordinator就认为consumer已经挂掉了,就会开启新一轮的rebalance,并且在当前其他consumer的心跳response中添加"REBALANCE_IN_PROGRESS"
rebalance过程
在rebalance之前首先需要确定coordinator,rebalance分为两大步:
-
Join:加入组:
- 所有成员向coordinator发送JoinGroup请求,请求入组
- coordinator收到
所有成员
的请求后,会从中选择一个作为Leader(注意:coordinator在收到所有成员请求之前,它会把请求放入一个叫purgatory的地方) - coordinator会将成员信息以及订阅信息发送给Leader
-
Sync:分配消费方案:
- Leader负责分配消费方案,即确定哪个consumer负责消费哪些topic的哪些partition
- 分配完成,Leader将分配方案封装进SyncGroup请求中发送给coordinator,非Leader也会发SyncGroup请求,只是内容为空
- coordinator接收到分配方案后会将方案放入SyncGroup的response中发送给各个consumer,这时组内成员就知道自己应该消费哪些分区了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-To7cCeAL-1583333635388)(images/Rebalance过程1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nkK7Thq2-1583333635391)(images/Rebalance过程2.png)]
consumer group的分区分配方案是在客户端执行的!Kafka将这个权利下放给客户端主要是因为这样做可以有更好的灵活性。Kafka默认提供了两种分配策略:range和round-robin。可以覆盖consumer的参数:partition.assignment.strategy来实现自己分配策略就好了
consumer group状态
consumer group也有状态机表明组状态的流转。coordinator会根据这个状态会对group做不同的处理
- Dead:组内已经没有任何成员的最终状态,组的元数据已经被coordinator移除。这种状态响应各种请求都是一个response:UNKNOWN_MEMBER_ID
- Empty:组内无成员,但位移信息还没有过期,这种状态只能响应JoinGroup请求
- PreparingRebalance:组准备开启新的rebalance,等待成员加入
- AwaitingSync:正在等待Leader consumer将分配方案传给各个成员
- Stable:rebalance完成,可以开始消费了
rebalance应用场景
- 新成员加入组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DLdxjM6r-1583333635392)(images/新成员加入组.png)]
- 组成员崩溃
组成员崩溃和组成员主动离开是两个不同的场景。因为在崩溃时成员并不会主动地告知coordinator此事,coordinator有可能需要一个完整的session.timeout周期才能检测到这种崩溃,这必然会造成consumer的滞后。
可以说离开组是主动地发起rebalance;而崩溃则是被动地发起rebalance
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-avhNV451-1583333635394)(images/组成员崩溃.png)]
- 组成员主动离组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iEW7DPh8-1583333635395)(images/组成员主动离组.png)]
- 提交位移
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FxLgdtCq-1583333635397)(images/提交位移.png)]
[外链图片转存中…(img-avhNV451-1583333635394)]
- 组成员主动离组
[外链图片转存中…(img-iEW7DPh8-1583333635395)]
- 提交位移
[外链图片转存中…(img-FxLgdtCq-1583333635397)]