重平衡是所有消息系统中非常重要的一个功能。试想有以下场景,当消费者组集群中有新的消费实例加入,退出,或者某些topic主题增加了分区之后,或者某些topic主题缩容后,消费者组实例是怎么对这些分区进行消费呢?当发生这些变化时,消息系统需要对这些消费组实例对应的分区进行重新分配,这个重新分配的过程,就是重平衡。当重平衡结束后,消费端会根据最新的重平衡结果进行重新消费。
在介绍重平衡之前先介绍一下消费者组的模型,如下图所示:
图1 消费者组的模型
一个分区的消息默认只能被一个消费者组下的实例所消费,一个消费者可以消费多个分区,在大部分情况下,每条消息只会被同一个消费组的某一个消费者消费,消息不会被重复消费。重平衡的本质是重新分配消费者组下分区与消费者实例的对应关系。下面会来详细介绍一下重平衡过程。在介绍PMQ重平衡之前,先介绍一下Kafka的重平衡机制。
Kafka 为消费组定义了 5 个状态,描述如下:
可以看出,重平衡发生在 Preparing 和 AwaitingSync 状态机中,重平衡主要包括以下两个步骤:
图2 加入组流程
图3 同步更新流程
上面介绍了Kafka的重平衡机制,下面来介绍PMQ重平衡机制。在PMQ中,引入了一个集中式的重平衡器的组件,它会去监控所有与消费者组相关的变更事件,比如说客户端的加入,退出,扩容,缩容等。当这些事件发生变更时,会自动向元数据库中插入一个变更记录,然后重平衡器会监听这些记录,一旦发现有新的记录产生的时候,重平衡器会立马去触发重平衡操作。重平衡的过程如下:
当消费者实例在发布的时候,会出现多次重平衡的情况。比如当发布完消费实例一,会发生一次重平衡,发布完实例二,又会触发一次重平衡。此时如果消费端每次重平衡完成后就立刻进行新的消费,就会产生消息重复消费的问题。那PMQ是如何做到重平衡的时候,尽量去减轻重复消费的影响呢?我们引入一个概念--重平衡稳定态。什么叫做平衡稳定态,就是说当一个客户端加入、退出或者扩容了以后,客户端连续比对服务端集群中的版本号,当消费者组重平衡版本号连续三次或者连续四次比对(次数和比较的时间间隔可以配置)没有发生变化时,我们就认为这个消费者重平衡已经进入到稳定的状态。PMQ中通过这种方式来减少由于重平衡可能会出现重复消费的情况,整个重平衡过程如下,
图4 PMQ重平衡流程
注: C1, C2, C3代表一个consumer组中的实例。Queue1,Queue2,Queue3代表一个消费者组中的queue。
在PMQ中,重平衡采取集中式重平衡的方法,好处是我们可以很好的控制重平衡的方式,比如我们可以设置消费者组的黑白名单,让指定的消费者实例进行消费或者不消费。黑白名单在消息系统中是非常有用的功能,比如在开发环境进行本地调试的时候,我就可以设置白名单,让消息只被本地实例消费。我们也可以设置黑名单,让本地开发实例不消费。缺点是当大批量发生重平衡时,会影响重平衡速度。在Kafka中, 重平衡在客户端消费者组实例中进行。如果需要优化重平衡的方式,客户端需要升级发版等。好处是重平衡的并发性好,可以同时进行多个重平衡。
除了重平衡方式不同之外,重平衡的数据方式也会有不同。在PMQ中,目前是采取的是对消费者组下所有的队列进行平均分配。在Kafka中默认也是采取这种方式,但是Kafka还提供了一种按照消费者组下的topic进行重平衡分配的功能,好处在于重平衡分配会比较稳定,分配前与分配后保持一致,但是可能会出现分配不均衡和影响消费速度的情况。按照队列进行重平衡分配的好处是每个消费者实例分配到的队列会比较均衡,缺点是重平衡前后,队列分配波动较大。
重平衡的过程会对消费端消息消费产生以下影响:
至此,对于PMQ的重平衡机制,已介绍完毕。目前PMQ也已经在github上开源(https://github.com/ppdaicorp/pmq),欢迎大家一起交流学习。
lorgine, 信也科技资深架构师
terry,基础架构研发高级工程师