消费者组、位移主题、重平衡
参考 Kafka 核心技术与实战 (opens new window) 第 15-17 讲
# 1. 消费者组
# 1.1 消费者组的设计特性
消费者组(Consumer Group)是 Kafka 提供的可扩展且具有容错性的消费者机制。既然是一个组,那么组内必然可以有多个消费者或消费者实例(Consumer Instance),它们共享一个公共的 ID,这个 ID 被称为 Group ID。组内的所有消费者协调在一起来消费订阅主题(Subscribed Topics)的所有分区(Partition)。当然,每个分区只能由同一个消费者组内的一个 Consumer 实例来消费。
个人认为,理解 Consumer Group 记住下面这三个特性就好了:
- Consumer Group 下可以有一个或多个 Consumer 实例。这里的实例可以是一个单独的进程,也可以是同一进程下的线程。在实际场景中,使用进程更为常见一些。
- Group ID 是一个字符串,在一个 Kafka 集群中,它标识唯一的一个 Consumer Group。
- Consumer Group 下所有实例订阅的主题的单个分区,只能分配给组内的某个 Consumer 实例消费。这个分区当然也可以被其他的 Group 消费。
Consumer Group 的机制兼具了“点对点模型”和“发布/订阅模型”的优点,当 Consumer Group 订阅了多个主题后,组内的每个实例不要求一定要订阅主题的所有分区,它只会消费部分分区中的消息。这种方式增强了架构的伸缩性。
- 点对点模型中,消息一旦被消费就从队列中删除,多个 Consumer 都要抢这个共享消息队列的消息,导致伸缩性不好。
- 发布/订阅模型中,每个订阅者都必须要订阅 topic 的所有分区,不灵活,伸缩性也不好。
Consumer Group 之间彼此独立,互不影响,它们能够订阅相同的一组主题而互不干涉。再加上 Broker 端的消息留存机制,Kafka 的 Consumer Group 完美地规避了上面提到的伸缩性差的问题。可以这么说,Kafka 仅仅使用 Consumer Group 这一种机制,却同时实现了传统消息引擎系统的两大模型。因为点对点模型和发布/订阅模型都算是 Consumer Group 机制的特殊情况。
# 1.2 一个 Group 下应该有多少 Consumer 实例?
理想情况下,Consumer 实例的数量应该等于该 Group 订阅主题的分区总数。
举个简单的例子,假设一个 Consumer Group 订阅了 3 个主题,分别是 A、B、C,它们的分区数依次是 1、2、3,那么通常情况下,为该 Group 设置 6 个 Consumer 实例是比较理想的情形,因为它能最大限度地实现高伸缩性。
你可能会问,我能设置小于或大于 6 的实例吗?当然可以!如果你有 3 个实例,那么平均下来每个实例大约消费 2 个分区(6 / 3 = 2);如果你设置了 8 个实例,那么很遗憾,有 2 个实例(8 – 6 = 2)将不会被分配任何分区,它们永远处于空闲状态。因此,在实际使用过程中一般不推荐设置大于总分区数的 Consumer 实例。设置多余的实例只会浪费资源,而没有任何好处。
# 1.3 Consumer Group 中如何管理 offset
下面讨论一个问题:针对 Consumer Group,Kafka 是怎么管理位移的呢?你还记得吧,消费者在消费的过程中需要记录自己消费了多少数据,即消费位置信息。在 Kafka 中,这个位置信息有个专门的术语:位移(Offset)。
看上去该 Offset 就是一个数值而已,其实对于 Consumer Group 而言,它是一组 KV 对,Key 是分区,V 对应 Consumer 消费该分区的最新位移。如果用 Java 来表示的话,你大致可以认为是这样的数据结构,即 Map<TopicPartition, Long>
,其中 TopicPartition 表示一个分区,而 Long 表示位移的类型。当然,我必须承认 Kafka 源码中并不是这样简单的数据结构,而是要比这个复杂得多,不过这并不会妨碍我们对 Group 位移的理解。
在旧版 Consumer Group 中,会把 offset 保存在 ZooKeeper 中,这样减少了 Kafka Broker 端的状态保存开销,从而实现超强的伸缩性。但后来人们发现,Zookeeper 这类元框架并不适合频繁的写更新,而 Consumer Group 的 offset 更新却是一个非常频繁的操作,这种大吞吐量的写操作会极大地拖慢 Zookeeper 集群的性能,因此 Kafka 社区渐渐形成共识:将 Co你sumer offset 保存在 Zookeeper 中是不合适的。
于是,在新版本的 Consumer Group 中,Kafka 社区重新设计了 Consumer Group 的位移管理方式,采用了将位移保存在 Kafka 内部主题的方法。这个内部主题就是让人既爱又恨的 __consumer_offsets。后面会对这个进行专门讲解。
# 1.4 Rebalance
# 1.4.1 什么是 Rebalance?
最后,我们来说说 Consumer Group 端大名鼎鼎的重平衡,也就是所谓的 Rebalance 过程。我形容其为“大名鼎鼎”,从某种程度上来说其实也是“臭名昭著”,因为有关它的 bug 真可谓是此起彼伏,从未间断。这里我先卖个关子,后面我会解释它“遭人恨”的地方。我们先来了解一下什么是 Rebalance。
Rebalance 本质上是一种协议,规定了一个 Consumer Group 下的所有 Consumer 如何达成一致,来分配订阅 Topic 的每个分区。比如某个 Group 下有 20 个 Consumer 实例,它订阅了一个具有 100 个分区的 Topic。正常情况下,Kafka 平均会为每个 Consumer 分配 5 个分区。这个分配的过程就叫 Rebalance。
Rebalance 的触发条件有 3 个:
- 组成员数发生变更。
- Group 订阅的主题数发生变更。
- 订阅主题的分区数发生变更。当分区数增加时,就会触发订阅该主题的所有 Group 开启 Rebalance。
Rebalance 发生时,Group 下所有的 Consumer 实例都会协调在一起共同参与。你可能会问,每个 Consumer 实例怎么知道应该消费订阅主题的哪些分区呢?这就需要分配策略的协助了。
当前 Kafka 默认提供了 3 种分配策略,每种策略都有一定的优势和劣势,我们今天就不展开讨论了,你只需要记住社区会不断地完善这些策略,保证提供最公平的分配策略,即每个 Consumer 实例都能够得到较为平均的分区数。比如一个 Group 内有 10 个 Consumer 实例,要消费 100 个分区,理想的分配策略自然是每个实例平均得到 10 个分区。这就叫公平的分配策略。如果出现了严重的分配倾斜,势必会出现这种情况:有的实例会“闲死”,而有的实例则会“忙死”。
下图展示了一个 Rebalance 的示例:
# 1.4.2 Rebalance 遭人恨的地方
- Rebalance 过程会 stop the word,所有 Consumer 实例都会停止消费,等待 Rebalance 完成。
- 目前 Rebalance 的设计是所有 Consumer 实例共同参与,全部重新分配所有分区。但其实更高效的做法是尽量减少分配方案的变动,从而减少 socket 资源的重新创建。
- Rebalance 太慢了。曾经,有个国外用户的 Group 内有几百个 Consumer 实例,成功 Rebalance 一次要几个小时!这完全是不能忍受的。最悲剧的是,目前社区对此无能为力,至少现在还没有特别好的解决方案。
所谓“本事大不如不摊上”,也许最好的解决方案就是避免 Rebalance 的发生吧。
# 1.5 小结
这一章介绍了 Kafka Consumer Group 的定义、特性以及它的唯一管理和 Rebalance 过程。
# 2. 位移主题
这一章主要分享的内容是:Kafka 中神秘的内部主题(Internal Topic)__consumer_offsets。
__consumer_offsets 在 Kafka 源码中有个更为正式的名字,叫位移主题,即 Offsets Topic。
为了方便今天的讨论,我将统一使用位移主题来指代 __consumer_offsets。需要注意的是,它有两个下划线哦。
# 2.1 位移主题的前世今生
之前说过老版本 Consumer 的位移管理是依托于 Apache ZooKeeper 的,它会自动或手动地将位移数据提交到 ZooKeeper 中保存。当 Consumer 重启后,它能自动从 ZooKeeper 中读取位移数据,从而在上次消费截止的地方继续消费。这种设计使得 Kafka Broker 不需要保存位移数据,减少了 Broker 端需要持有的状态空间,因而有利于实现高伸缩性。
但是,ZooKeeper 其实并不适用于这种高频的写操作,因此,Kafka 社区自 0.8.2.x 版本开始,就在酝酿修改这种设计,并最终在新版本 Consumer 中正式推出了全新的位移管理机制,自然也包括这个新的位移主题。
新版本 Consumer 的位移管理机制其实也很简单,就是将 Consumer 的位移数据作为一条条普通的 Kafka 消息,提交到 __consumer_offsets 主题中。可以这么说,__consumer_offsets 的主要作用是保存 Kafka 消费者的位移信息。它要求这个提交过程不仅要实现高持久性,还要支持高频的写操作。显然,Kafka 的主题设计天然就满足这两个条件,因此,使用 Kafka 主题来保存位移这件事情,实际上就是一个水到渠成的想法了。
这里我想再次强调一下,和你创建的其他主题一样,位移主题就是普通的 Kafka 主题。你可以手动地创建它、修改它,甚至是删除它。只不过,它同时也是一个内部主题,大部分情况下,你其实并不需要“搭理”它,也不用花心思去管理它,把它丢给 Kafka 就完事了。
位移主题中的消息格式是 kafka 自己定义的,用户不能修改,也就是说你不能随意地向这个主题写消息,因为一旦你写入的消息不满足 Kafka 规定的格式,那么 Kafka 内部无法成功解析,就会造成 Broker 的崩溃。事实上,Kafka Consumer 有 API 帮你提交位移,也就是向位移主题写消息。你千万不要自己写个 Producer 随意向该主题发送消息。
# 2.2 位移主题中的消息格式
位移主题中的消息格式其实就是 KV pair,其中 key 保存了三部分内容:Group ID、主题名、分区号。这样就能记录每个 Consumer 的位移数据。
然后消息体部分主要就是保存了位移值,实际实现中还会保存一些时间戳等元数据来方便后续操作。
当然了,位移主题的消息格式可不是只有上面说的这一种。事实上,它有 3 种消息格式。除了刚刚我们说的这种格式,还有 2 种格式:
- 用于保存 Consumer Group 信息的消息。
- 用于删除 Group 过期位移甚至是删除 Group 的消息。
第 1 种格式非常神秘,以至于你几乎无法在搜索引擎中搜到它的身影。不过,你只需要记住它是用来注册 Consumer Group 的就可以了。
第 2 种格式相对更加有名一些。它有个专属的名字:tombstone 消息,即墓碑消息,也称 delete mark。下次你在 Google 或百度中见到这些词,不用感到惊讶,它们指的是一个东西。这些消息只出现在源码中而不暴露给你。它的主要特点是它的消息体是 null,即空消息体。
什么时候会写入这类消息呢?一旦某个 Consumer Group 下的所有 Consumer 实例都停止了,而且它们的位移数据都已被删除时,Kafka 会向位移主题的对应分区写入 tombstone 消息,表明要彻底删除这个 Group 的信息。
# 2.3 位移主题的创建
位移主题是怎么被创建的。通常来说,当 Kafka 集群中的第一个 Consumer 程序启动时,Kafka 会自动创建位移主题。
我们说过,位移主题就是普通的 Kafka 主题,那它的分区数、副本数怎么控制:
- Broker 端参数 offsets.topic.num.partitions:位移主题的分区数,默认值 50
- Broker 端参数 offsets.topic.replication.factor:副本数,默认值 3
总结一下,如果位移主题是 Kafka 自动创建的,那么该主题的分区数是 50,副本数是 3。
虽然也可以手动创建位移主题,但最好还是让 Kafka 自动创建比较好,以免出现奇怪的问题。
# 2.4 位移主题的使用
什么地方会用到位移主题呢?前面我们说了 Kafka 提交位移时会写入该主题,目前 Consumer 提交位移的方式有两种:自动提交位移和手动提交位移。
Consumer 端有个参数叫 enable.auto.commit,如果值是 true,则 Consumer 在后台默默地为你定期提交位移,提交间隔由一个专属的参数 auto.commit.interval.ms 来控制。自动提交位移有一个显著的优点,就是省事,你不用操心位移提交的事情,就能保证消息消费不会丢失。但这一点同时也是缺点。因为它太省事了,以至于丧失了很大的灵活性和可控性,你完全没法把控 Consumer 端的位移管理。
事实上,很多与 Kafka 集成的大数据框架都是禁用自动提交位移的,如 Spark、Flink 等。这就引出了另一种位移提交方式:手动提交位移,即设置 enable.auto.commit = false。一旦设置了 false,作为 Consumer 应用开发的你就要承担起位移提交的责任。Kafka Consumer API 为你提供了位移提交的方法,如 consumer.commitSync 等。当调用这些方法时,Kafka 会向位移主题写入相应的消息。
如果你选择的是自动提交位移,那么就可能存在一个问题:只要 Consumer 一直启动着,它就会无限期地向位移主题写入消息。
我们来举个极端一点的例子。假设 Consumer 当前消费到了某个主题的最新一条消息,位移是 100,之后该主题没有任何新消息产生,故 Consumer 无消息可消费了,所以位移永远保持在 100。由于是自动提交位移,位移主题中会不停地写入位移 =100 的消息。显然 Kafka 只需要保留这类消息中的最新一条就可以了,之前的消息都是可以删除的。这就要求 Kafka 必须要有针对位移主题消息特点的消息删除策略,否则这种消息会越来越多,最终撑爆整个磁盘。
# 2.5 Compaction 策略
Kafka 删除位移主题中过期消息的策略是:Compaction 策略。
对于同一个 Key 的两条消息 M1 和 M2,如果 M1 的发送时间早于 M2,那么 M1 就是过期消息。
Compact 的过程就是扫描日志的所有消息,剔除那些过期的消息,然后把剩下的消息整理在一起。我在这里贴一张来自官网的图片,来说明 Compact 过程:
Compaction 单词在 JVM 垃圾回收中的术语翻译为“整理”,它与 JVM 的“标记整理法”做法也很类似。
Kafka 提供了专门的后台线程定期地巡检待 Compact 的主题,看看是否存在满足条件的可删除数据。这个后台线程叫 Log Cleaner。很多实际生产环境中都出现过位移主题无限膨胀占用过多磁盘空间的问题,如果你的环境中也有这个问题,我建议你去检查一下 Log Cleaner 线程的状态,通常都是这个线程挂掉了导致的。
# 2.6 小结
今天分享了 Kafka 的位移主题 __consumer_offsets,包括引入它的契机与原因、它的作用、消息格式、写入的时机以及管理策略等,这对我们了解 Kafka 特别是 Kafka Consumer 的位移管理是大有帮助的。
实际上,将很多元数据以消息的方式存入 Kafka 内部主题的做法越来越流行。除了 Consumer 位移管理,Kafka 事务也是利用了这个方法,当然那是另外的一个内部主题了。这种做法背后的想法也很简单:既然 Kafka 天然实现了高持久性和高吞吐量,那么任何有这两个需求的子服务自然也就不必求助于外部系统,用 Kafka 自己实现就好了。
# 3. 消费者组重平衡能避免吗?
前面已经介绍了 Rebalance,这里回顾一下概念:Rebalance 就是让一个 Consumer Group 下所有的 Consumer 实例就如何消费订阅主题的所有分区达成共识的过程。在 Rebalance 过程中,所有 Consumer 实例共同参与,在协调者组件的帮助下,完成订阅主题分区的分配。但是,在整个过程中,所有实例都不能消费任何消息,因此它对 Consumer 的 TPS 影响很大。
# 3.1 Coordinator 组件
协调者(Coordinator):专门为 Consumer Group 服务,负责为 Group 执行 Rebalance 以及提供位移管理和组成员管理等。
具体来讲,Consumer 端应用程序在提交位移时,其实是向 Coordinator 所在的 Broker 提交位移。同样地,当 Consumer 应用启动时,也是向 Coordinator 所在的 Broker 发送各种请求,然后由 Coordinator 负责执行消费者组的注册、成员管理记录等元数据管理操作。
所有 Broker 在启动时,都会创建和开启相应的 Coordinator 组件。也就是说,所有 Broker 都有各自的 Coordinator 组件。那么,Consumer Group 如何确定为它服务的 Coordinator 在哪台 Broker 上呢?答案就在我们之前说过的 Kafka 内部位移主题 __consumer_offsets 身上。
目前,Kafka 为某个 Consumer Group 确定 Coordinator 所在的 Broker 的算法有 2 个步骤:
- 确定由位移主题的哪个分区来保存该 Group 数据:
partitionId=Math.abs(groupId.hashCode() % 位移主题的分区数)
。 - 找出该分区 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 应用程序,特别是 Java Consumer API,能够自动发现并连接正确的 Coordinator,我们不用操心这个问题。知晓这个算法的最大意义在于,它能够帮助我们解决定位问题。当 Consumer Group 出现问题,需要快速排查 Broker 端日志时,我们能够根据这个算法准确定位 Coordinator 对应的 Broker,不必一台 Broker 一台 Broker 地盲查。
# 3.2 Rebalance 的弊端
我们说要避免 Rebalance,那它的弊端是什么呢?总结有如下三点:
- Rebalance 影响 Consumer 端 TPS。因为它会 stop the world。
- Rebalance 过程很慢。几百个 Consumer 实例 Rebalance 一次要几个小时。
- Rebalance 效率不高。当前 Kafka 的设计机制决定了每次 Rebalance 时,Group 下的所有成员都要参与进来,而且通常不会考虑局部性原理,但局部性原理对提升系统性能是特别重要的。
关于第 3 点,我们来举个简单的例子。比如一个 Group 下有 10 个成员,每个成员平均消费 5 个分区。假设现在有一个成员退出了,此时就需要开启新一轮的 Rebalance,把这个成员之前负责的 5 个分区“转移”给其他成员。显然,比较好的做法是维持当前 9 个成员消费分区的方案不变,然后将 5 个分区随机分配给这 9 个成员,这样能最大限度地减少 Rebalance 对剩余 Consumer 成员的冲击。
但遗憾的是,目前 Kafka 并没有这样设计,根本没有考虑局部性原理。
上面的弊端中,影响 TPS 和 Rebalance 慢的问题基本是“无解”的,所以我们只能尽力去避免 Rebalance。
就我个人经验而言,在真实的业务场景中,很多 Rebalance 都是计划外的或者说是不必要的。我们应用的 TPS 大多是被这类 Rebalance 拖慢的,因此避免这类 Rebalance 就显得很有必要了。下面我们就来说说如何避免 Rebalance。
# 3.3 Rebalance 该如何避免
# 3.3.1 需要在意哪种情况下的 Rebalance?
Rebalance 发生的时机有三个:
- 组成员数量发生变化
- 订阅主题数量发生变化
- 订阅主题的分区数发生变化
后面两个通常都是运维的主动操作,所以它们引发的 Rebalance 大都是不可避免的。接下来,我们主要说说因为组成员数量变化而引发的 Rebalance 该如何避免。
如果 Consumer Group 下的 Consumer 实例数量发生变化,就一定会引发 Rebalance。这是 Rebalance 发生的最常见的原因。我碰到的 99% 的 Rebalance,都是这个原因导致的。
Consumer 实例增加的情况很好理解,当我们启动一个配置有相同 group.id 值的 Consumer 程序时,实际上就向这个 Group 添加了一个新的 Consumer 实例。此时,Coordinator 会接纳这个新实例,将其加入到组中,并重新分配分区。通常来说,增加 Consumer 实例的操作都是计划内的,可能是出于增加 TPS 或提高伸缩性的需要。总之,它不属于我们要规避的那类“不必要 Rebalance”。
我们更在意的是 Group 下实例数减少这件事。如果你就是要停掉某些 Consumer 实例,那自不必说,关键是在某些情况下,Consumer 实例会被 Coordinator 错误地认为“已停止”从而被“踢出”Group。如果是这个原因导致的 Rebalance,我们就不能不管了。
# 3.3.2 Consumer 实例被误认为挂掉的原因
Coordinator 会在什么情况下认为某个 Consumer 实例已挂从而要退组呢?这个绝对是需要好好讨论的话题,我们来详细说说。
每个 Consumer 实例需要定期向 Coordinator 发送心跳请求来表明自己存活,如果超时未发送,则被 Coordinator 认为这个实例死了,从而将其从 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 端还有一个参数 max.poll.interval.ms
用于控制 Consumer 实际消费能力对 Rebalance 的影响。它限定了 Consumer 端应用程序两次调用 poll 方法的最大时间间隔。它的默认值是 5 分钟,表示你的 Consumer 程序如果在 5 分钟之内无法消费完 poll 方法返回的消息,那么 Consumer 会主动发起“离开组”的请求,Coordinator 也会开启新一轮 Rebalance。
搞清楚了这些参数的含义,接下来我们来明确一下到底哪些 Rebalance 是“不必要的”。
# 3.3.3 哪些 Rebalance 是不必要的?
第一类非必要 Rebalance 是因为未能及时发送心跳,导致 Consumer 被“踢出”Group 而引发的。因此,你需要仔细地设置 session.timeout.ms 和 heartbeat.interval.ms 的值。我在这里给出一些推荐数值,你可以“无脑”地应用在你的生产环境中。
- 设置 session.timeout.ms = 6s。
- 设置 heartbeat.interval.ms = 2s。
- 要保证 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 导致的长时间停顿,从而引发了 Rebalance。为什么特意说 GC?那是因为在实际场景中,我见过太多因为 GC 设置不合理导致程序频发 Full GC 而引发的非预期 Rebalance 了。
# 3.4 小结
总而言之,我们一定要避免因为各种参数或逻辑不合理而导致的组成员意外离组或退出的情形,与之相关的主要参数有:
- session.timeout.ms
- heartbeat.interval.ms
- max.poll.interval.ms
- GC 参数
按照我们今天所说的内容,恰当地设置这些参数,你一定能够大幅度地降低生产环境中的 Rebalance 数量,从而整体提升 Consumer 端 TPS。