基础概念
Rebalance(再均衡)指的是:将一个 topic 下的多个队列(或称之为分区),在同一个消费者组(consumer group)下的多个消费者实例(consumer)之间进行重新分配。
Rebalance 本意是为了提升消息的「并行处理」能力。
触发时机
Consumer rebalance 的触发时机:topic 的 queue 数量发生变化或 consumer 数量发生变化时
比方说如下的场景:
- 日常发布过程中 consumer 实例的停止与启动
- Consumer 消费者实例异常宕机
- 网络异常导致 consumer 消费者实例和 Broker 断开链接
- 主动进行 consumer group 下消费者数量扩容/缩容
- Topic queue 主动进行扩容/缩容
机制剖析
下面是纯理论的知识,具体的逻辑还需要去对照源码进行学习。
Broker 端通过以下三个组件共同维护 consumer group 的元信息:
- ConsumerManager: ConsumerManager 是最重要的一个消费者组元数据管理器,其维护了某个消费者组的订阅信息,以及所有消费者实例的详细信息,并在发生变化时提供通知机制。
- ConsumerOffsetManager: 维护 offset 进度信息
- SubscriptionGroupManager: 运维相关操作信息维护
Broker 在 Rebalance 过程中,是一个协调者的角色,它会在消费者组信息或者 topic 信息发生变化时「通知每个消费者各自 rebalance」,即每个消费者自己给自己重新分配队列,而不是 broker 将分配好的结果通知 consumer.
从这个角度看,RocketMQ 与 Kafka Rebalance 机制类似,二者 Rebalance 分配都是在客户端进行,不同的是:
- Kafka: 会在消费者组的多个消费者实例中,选出一个作为 Group Leader,由这个 Group Leader 来进行分区分配,分配结果通过 Coordinator(特殊角色的 Broker)同步给其他消费者。相当于 Kafka 的分区分配只有一个大脑,就是 Group Leader.
- RocketMQ: 每个消费者,自己负责给自己分配队列,相当于每个消费者都是一个大脑。
限制
Topic 的一个队列 queue 最多分配给一个消费者 consumer(不考虑公司内魔改的情况),因此当某个 consumer group 下的消费者实例大于队列的数量时,多余的消费者实例将分配不到任何队列。
危害
Rebalance 时,也会对消费造成一些不良影响,主要原因是由于
- Rebalance 期间,消费者需要暂停消费等待 Rebalance 结束
- Rebalance 结束后,新连接的消费者需要从之前 queue 已提交的 offset 重新开始消费
具体的影响面如下所述:
- 消费暂停:假设 consumer1 当前消费 queue1, queue2 两个队列,当 consumer1 下线触发 rebalance 时,这两个队列就需要暂停,等到重新分配给 consumer2 以后,这两个队列才能继续消费。
解决方案:做好消费暂停报警,及时感知。 - 重复消费:consumer2 在消费分配给自己的两个队列时,必须接着从 broker 中获取到 consumer1 已经提交的 offset 的 +1,才开始继续消费。然而默认情况下,offset 是异步提交给 broker 的,比如 consumer1 当前消费到 offset 为 10,但是异步提交给 broker 的 offset 为 8;如果此时 consumer2 从 offset = 8 继续开始消费,就会有两条消息重复。也就是说,consumer2 并不会等待 consumer1 提交完 offset 以后,在进行 rebalance,因此提交间隔越长,造成的重复消费就越多。
解决方案:这个时候就需要业务接入方能够自己去实现「幂等控制」,基于「一锁二判三更新」的原则,基于状态机、流水表、业务唯一索引进行重复操作判断。 - 消费突刺:由上面两个问题衍生而来,由于 rebalance 可能导致重复消费,如果需要重复消费的消息过多;或者因为 rebalance 暂停时间过长,导致积压了部分消息,都有可能导致 rebalance 结束之后瞬间需要消费很多消息。
解决方案:做好集群动态扩容,避免消息堆积;做好消费突刺限流,避免打挂系统和下游
结合前面的 rebalance 触发时机可以看出来,其实这些危害出现的时机并不严格,但是我们平常写业务代码的时候,又几乎没有做消费暂停报警、幂等、消费突刺限流的控制,可谓是面向不稳定的编程了,代码的健壮性想来其实也不算很高,有「代码洁癖」的话,确实是需要再改进下。
但是话说回来,做太严格的控制,有可能会因为场景不复杂,写复杂度较高的代码在 CR 被打回来,原由是过度编程了,这就是大家见仁见智了,团队项目里可以不这么写,但是得懂这种场景可能造成的不良影响。