kafak入门 第八篇 消费者组重平衡能避免吗?以及Consumer相关参数介绍
Rebalance 就是让⼀个 Consumer Group 下所有的 Consumer 实例就如何消费订阅 主题的所有分区达成共识的过程。在 Rebalance 过程中,所有 Consumer 实例共同参与,在协调者(Coordinator)组件的帮助下,完成订阅主题分区的分配。但是,在整个过程中,所有实例都不能消费任何 消息,因此它对 Consumer 的 TPS 影响很⼤。
具体来说,Consumer端应用程序在提交位移时,其实是向Coordinator所在的Broker提交位移。同样地,当Consumer应用启动的时候,也是向Coordinator所在的Broker发送各种请求,然后由Coordinator负责执行消费组的注册、成员管理记录等元数据管理操作。
所有的Broker在启动的时候,都会创建和开启相应的Coordinator组件。也就是说,所有的Broker都有Coordinator都有各自的Coordinator组件。那么,ConsumerGroup如何确定为他服务的Coordinato在哪台Broker上呢?答案就在我们之前说过的kafka内部位移主题__consumer_offsets身上。
目前,kafka为某个ConsumerGroup确定Coordinato的所在Broker的算法有2个步骤。
第 1 步:确定由位移主题的哪个分区来保存该 Group 数据:
partitionId=Math.abs(groupId.hashCode() % offsetsTopicPartitionCount)。
第 2 步:找出该分区 Leader 副本所在的 Broker,该 Broker 即为对应的 Coordinator。
简单解释⼀下上⾯的算法。⾸先,Kafka 会计算该 Group 的 group.id 参数的哈希值。⽐如你有 个 Group 的 group.id 设置成了“test-group”,那么它的 hashCode 值就应该是 627841412。 其次,Kafka 会计算 __consumer_offsets 的分区数,通常是 50 个分区,之后将刚才那个哈希 值对分区数进⾏取模加求绝对值计算,即 abs(627841412 % 50) = 12。此时,我们就知道了位 移主题的分区 12 负责保存这个 Group 的数据。有了分区号,算法的第 2 步就变得很简单了,我 们只需要找出位移主题分区 12 的 Leader 副本在哪个 Broker 上就可以了。这个 Broker,就是 我们要找的 Coordinator。
在实际使用中,Consumer应用程序能够自动自动发现并连接正确的Coordinator.
Rebalance的弊端:
1.Rebalance影响Consumer端的TPS,因为在重组期间Consumer是不能处理除了重组以外的任何事情
2.Rebalance很耗时间
3.Rebalance的效率不高
关于第 3 点,我们来举个简单的例⼦。⽐如⼀个 Group 下有 10 个成员,每个成员平均消费 5 个分区。假设现在有⼀个成员退出了,此时就需要开启新⼀轮的 Rebalance,把这个成员之前负 责的 5 个分区“转移”给其他成员。显然,⽐较好的做法是维持当前 9 个成员消费分区的⽅案不 变,然后将 5 个分区随机分配给这 9 个成员,这样能最⼤限度地减少 Rebalance 对剩余 Consumer 成员的冲击。遗憾的是,⽬前 Kafka 并不是这样设计的。在默认情况下,每次 Rebalance 时,之前的分配⽅ 案都不会被保留。就拿刚刚这个例⼦来说,当 Rebalance 开始时,Group 会打散这 50 个分区 (10 个成员 * 5 个分区),由当前存活的 9 个成员重新分配它们。显然这不是效率很⾼的做 法。基于这个原因,社区于 0.11.0.0 版本推出了 StickyAssignor,即有粘性的分区分配策略。所 谓的有粘性,是指每次 Rebalance 时,该策略会尽可能地保留之前的分配⽅案,尽量实现分区分 配的最⼩变动。不过有些遗憾的是,这个策略⽬前还有⼀些 bug,⽽且需要升级到 0.11.0.0 才能 使⽤,因此在实际⽣产环境中⽤得还不是很多。
要避免 Rebalance,还是要从 Rebalance 发⽣的时机⼊⼿。
我们在前⾯说过,Rebalance 发⽣ 的时机有三个:
1.组成员数量发⽣变化
2.订阅主题数量发⽣变化
3.订阅主题的分区数发⽣变化
后⾯两个通常都是运维的主动操作,所以它们引发的 Rebalance ⼤都是不可避免的。接下来,我 们主要说说因为组成员数量变化⽽引发的 Rebalance 该如何避免。
如果 Consumer Group 下的 Consumer 实例数量发⽣变化,就⼀定会引发 Rebalance。这是 Rebalance 发⽣的最常⻅的原因。我碰到的 99% 的 Rebalance,都是这个原因导致的。 Consumer 实例增加的情况很好理解,当我们启动⼀个配置有相同 group.id 值的 Consumer 程 序时,实际上就向这个 Group 添加了⼀个新的 Consumer 实例。此时,Coordinator 会接纳这 个新实例 将其加⼊到组中 并重新分配分区 通常来说 增加 Consumer 实例的操作都是计划 组成员数量发⽣变化 订阅主题数量发⽣变化 订阅主题的分区数发⽣变化
我们更在意的是Group下实列数减少这件事。在一些情况下,Consumer实列会被Coordinator 错误的认为“已停止”从而被“踢出”Group。如果是这个原因导致的Rebalance,我们就不能不管了。
Coordinator 会在什么情况下认为某个 Consumer 实例已挂从⽽要退组呢?这个绝对是需要好好 讨论的话题,我们来详细说说。 当 Consumer Group 完成 Rebalance 之后,每个 Consumer 实例都会定期地向 Coordinator 发送⼼跳请求,表明它还存活着。如果某个 Consumer 实例不能及时地发送这些⼼跳请求, Coordinator 就会认为该 Consumer 已经“死”了,从⽽将其从 Group 中移除,然后开启新⼀轮 Rebalance。Consumer 端有个参数,叫 session.timeout.ms,就是被⽤来表征此事的。该参数 的默认值是 10 秒,即如果 Coordinator 在 10 秒之内没有收到 Group 下某 Consumer 实例的⼼ 跳,它就会认为这个 Consumer 实例已经挂了。可以这么说,session.timout.ms 决定了 Consumer 存活性的时间间隔。 除了这个参数,Consumer 还提供了⼀个允许你控制发送⼼跳请求频率的参数,就是 heartbeat.interval.ms。这个值设置得越⼩,Consumer 实例发送⼼跳请求的频率就越⾼。频繁 地发送⼼跳请求会额外消耗带宽资源,但好处是能够更加快速地知晓当前是否开启 Rebalance, 因为,⽬前 Coordinator 通知各个 Consumer 实例开启 Rebalance 的⽅法,就是将 REBALANCE_NEEDED 标志封装进⼼跳请求的响应体中。
除了以上两个参数,Consumer 端还有⼀个参数,⽤于控制 Consumer 实际消费能⼒对 Rebalance 的影响,即 max.poll.interval.ms 参数。它限定了 Consumer 端应⽤程序两次调⽤ poll ⽅法的最⼤时间间隔。它的默认值是 5 分钟,表示你的 Consumer 程序如果在 5 分钟之内 ⽆法消费完 poll ⽅法返回的消息,那么 Consumer 会主动发起“离开组”的请求,Coordinator 也会开启新⼀轮 Rebalance。 搞清楚了这些参数的含义,接下来我们来明确⼀下到底哪些 Rebalance 是“不必要的”。
搞清楚了这些参数的含义,接下来我们来明确⼀下到底哪些 Rebalance 是“不必要的”。
第⼀类⾮必要 Rebalance 是因为未能及时发送⼼跳,导致 Consumer 被“踢出”Group ⽽引发 的。因此,你需要仔细地设置session.timeout.ms 和 heartbeat.interval.ms的值。我在这⾥给 出⼀些推荐数值,你可以“⽆脑”地应⽤在你的⽣产环境中。
1.设置 session.timeout.ms = 6s。
2.设置 heartbeat.interval.ms = 2s。
3.要保证 Consumer 实例在被判定为“dead”之前,能够发送⾄少 3 轮的⼼跳请求,即 session.timeout.ms >= 3 *heartbeat.interval.ms。
将 session.timeout.ms 设置成 6s 主要是为了让 Coordinator 能够更快地定位已经挂掉的 Consumer。毕竟,我们还是希望能尽快揪出那些“⼫位素餐”的 Consumer,早⽇把它们踢出 Group。希望这份配置能够较好地帮助你规避第⼀类“不必要”的 Rebalance。
第⼆类⾮必要 Rebalance 是 Consumer 消费时间过⻓导致的。我之前有⼀个客户,在他们的场 景中,Consumer 消费数据时需要将消息处理之后写⼊到 MongoDB。显然,这是⼀个很重的消 费逻辑。MongoDB 的⼀丁点不稳定都会导致 Consumer 程序消费时⻓的增加。此时, max.poll.interval.ms参数值的设置显得尤为关键。如果要避免⾮预期的 Rebalance,你最好将 该参数值设置得⼤⼀点,⽐你的下游最⼤处理时间稍⻓⼀点。就拿 MongoDB 这个例⼦来说,如 果写 MongoDB 的最⻓时间是 7 分钟,那么你可以将该参数设置为 8 分钟左右。 总之,你要为你的业务处理逻辑留下充⾜的时间。这样,Consumer 就不会因为处理这些消息的 时间太⻓⽽引发 Rebalance 了。 如果你按照上⾯的推荐数值恰当地设置了这⼏个参数,却发现还是出现了 Rebalance,那么我建 议你去排查⼀下Consumer 端的 GC 表现,⽐如是否出现了频繁的 Full GC 导致的⻓时间停顿,