写在前面

估计运维年前没有祭拜服务器,Nginx的问题修复了,Kafka又不行了。今天,本来想再睡会,结果,电话又响了。还是运营,“喂,冰河,到公司了吗?赶紧看看服务器吧,又出问题了“。“在路上了,运维那哥们儿还没上班吗”? “还在休假。。。”, 我:“。。。”。哎,这哥们儿是跑路了吗?先不管他,问题还是要解决。

问题重现

到公司后,放下我专用的双肩包,拿出我的利器——笔记本电脑,打开后迅速登录监控系统,发现主要业务系统没啥问题。一个非核心服务发出了告警,并且监控系统中显示这个服务频繁的抛出如下异常。

2021-02-28 22:03:05 131 pool-7-thread-3 ERROR [] -
commit failed
org.apache.kafka.clients.consumer.CommitFailedException: Commit cannot be completed since the group has already rebalanced and assigned the partitions to another member. This means that the time between subsequent calls to poll() was longer than the configured max.poll.interval.ms, which typically implies that the poll loop is spending too much time message processing. You can address this either by increasing the session timeout or by reducing the maximum size of batches returned in poll() with max.poll.records.
at org.apache.kafka.clients.consumer.internals.ConsumerCoordinator.sendOffsetCommitRequest(ConsumerCoordinator.java:713) ~[MsgAgent-jar-with-dependencies.jar:na]
at org.apache.kafka.clients.consumer.internals.ConsumerCoordinator.commitOffsetsSync(ConsumerCoordinator.java:596) ~[MsgAgent-jar-with-dependencies.jar:na]
at org.apache.kafka.clients.consumer.KafkaConsumer.commitSync(KafkaConsumer.java:1218) ~[MsgAgent-jar-with-dependencies.jar:na]
at com.today.eventbus.common.MsgConsumer.run(MsgConsumer.java:121) ~[MsgAgent-jar-with-dependencies.jar:na]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_161]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_161]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_161]

从上面输出的异常信息,大概可以判断出系统出现的问题:Kafka消费者在处理完一批poll消息后,在同步提交偏移量给broker时报错了。大概就是因为当前消费者线程的分区被broker给回收了,因为Kafka认为这个消费者挂掉了,我们可以从下面的输出信息中可以看出这一点。

Commit cannot be completed since the group has already rebalanced and assigned the partitions to another member. This means that the time between subsequent calls to poll() was longer than the configured max.poll.interval.ms, which typically implies that the poll loop is spending too much time message processing. You can address this either by increasing the session timeout or by reducing the maximum size of batches returned in poll() with max.poll.records.

Kafka内部触发了Rebalance机制,明确了问题,接下来,我们就开始分析问题了。

分析问题

既然Kafka触发了Rebalance机制,那我就来说说Kafka触发Rebalance的时机。

什么是Rebalance

举个具体点的例子,比如某个分组下有10个Consumer实例,这个分组订阅了一个50个分区的主题。正常情况下,Kafka会为每个消费者分配5个分区。这个分配的过程就是Rebalance。

触发Rebalance的时机

当Kafka中满足如下条件时,会触发Rebalance:

  • 组内成员的个数发生了变化,比如有新的消费者加入消费组,或者离开消费组。组成员离开消费组包含组成员崩溃或者主动离开消费组。
  • 订阅的主题个数发生了变化。
  • 订阅的主题分区数发生了变化。

后面两种情况我们可以人为的避免,在实际工作过程中,对于Kafka发生Rebalance最常见的原因是消费组成员的变化。

消费者成员正常的添加和停掉导致Rebalance,这种情况无法避免,但是时在某些情况下,Consumer 实例会被 Coordinator 错误地认为 “已停止” 从而被“踢出”Group,导致Rebalance。

当 Consumer Group 完成 Rebalance 之后,每个 Consumer 实例都会定期地向 Coordinator 发送心跳请求,表明它还存活着。如果某个 Consumer 实例不能及时地发送这些心跳请求,Coordinator 就会认为该 Consumer 已经 “死” 了,从而将其从 Group 中移除,然后开启新一轮 Rebalance。这个时间可以通过Consumer 端的参数 session.timeout.ms 进行配置。默认值是 10 秒。

除了这个参数,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 是因为未能及时发送心跳,导致 Consumer 被 “踢出”Group 而引发的。这种情况下我们可以设置 session.timeout.ms 和 heartbeat.interval.ms 的值,来尽量避免rebalance的出现。(以下的配置是在网上找到的最佳实践,暂时还没测试过

  • 设置 session.timeout.ms = 6s。
  • 设置 heartbeat.interval.ms = 2s。
  • 要保证 Consumer 实例在被判定为 “dead” 之前,能够发送至少 3 轮的心跳请求,即 session.timeout.ms >= 3 * heartbeat.interval.ms

将 session.timeout.ms 设置成 6s 主要是为了让 Coordinator 能够更快地定位已经挂掉的 Consumer,早日把它们踢出 Group。

第二类非必要 Rebalance 是 Consumer 消费时间过长导致的。此时,max.poll.interval.ms 参数值的设置显得尤为关键。如果要避免非预期的 Rebalance,最好将该参数值设置得大一点,比下游最大处理时间稍长一点。

总之,要为业务处理逻辑留下充足的时间。这样,Consumer 就不会因为处理这些消息的时间太长而引发 Rebalance 。

拉取偏移量与提交偏移量

kafka的偏移量(offset)是由消费者进行管理的,偏移量有两种,拉取偏移量(position)与提交偏移量(committed)。拉取偏移量代表当前消费者分区消费进度。每次消息消费后,需要提交偏移量。在提交偏移量时,kafka会使用拉取偏移量的值作为分区的提交偏移量发送给协调者。

如果没有提交偏移量,下一次消费者重新与broker连接后,会从当前消费者group已提交到broker的偏移量处开始消费。

所以,问题就在这里,当我们处理消息时间太长时,已经被broker剔除,提交偏移量又会报错。所以拉取偏移量没有提交到broker,分区又rebalance。下一次重新分配分区时,消费者会从最新的已提交偏移量处开始消费。这里就出现了重复消费的问题。

异常日志提示的方案

其实,说了这么多,Kafka消费者输出的异常日志中也给出了相应的解决方案。

接下来,我们说说Kafka中的拉取偏移量和提交偏移量。

其实,从输出的日志信息中,也大概给出了解决问题的方式,简单点来说,就是可以通过增加 max.poll.interval.ms 时长和 session.timeout.ms 时长,减少 max.poll.records的配置值,并且消费端在处理完消息时要及时提交偏移量。

问题解决

通过之前的分析,我们应该知道如何解决这个问题了。这里需要说一下的是,我在集成Kafka的时候,使用的是SpringBoot和Kafka消费监听器,消费端的主要代码结构如下所示。

@KafkaListener(topicPartitions = {@TopicPartition(topic = KafkaConstants.TOPIC_LOGS, partitions = { "0" }) }, groupId = "kafka-consumer", containerFactory = "kafkaListenerContainerFactory")
public void consumerReceive (ConsumerRecord<?, ?> record, Acknowledgment ack){
logger.info("topic is {}, offset is {}, value is {} n", record.topic(), record.offset(), record.value());
try {
Object value = record.value();
logger.info(value.toString());
ack.acknowledge();
} catch (Exception e) {
logger.error("日志消费端异常: {}", e);
}
}

上述代码逻辑比较简单,就是获取到Kafka中的消息后直接打印输出到日志文件中。

尝试解决

这里,我先根据异常日志的提示信息进行配置,所以,我在SpringBoot的application.yml文件中新增了如下配置信息。

spring:
kafka:
consumer:
properties:
max.poll.interval.ms: 3600000
max.poll.records: 50
session.timeout.ms: 60000
heartbeat.interval.ms: 3000

配置完成后,再次测试消费者逻辑,发现还是抛出Rebalance异常。

最终解决

我们从另一个角度来看下Kafka消费者所产生的问题:一个Consumer在生产消息,另一个Consumer在消费它的消息,它们不能在同一个groupId 下面,更改其中一个的groupId 即可。

这里,我们的业务项目是分模块和子系统进行开发的,例如模块A在生产消息,模块B消费模块A生产的消息。此时,修改配置参数,例如 session.timeout.ms: 60000,根本不起作用,还是抛出Rebalance 异常。

此时,我尝试修改下消费者分组的groupId,将下面的代码

@KafkaListener(topicPartitions = {@TopicPartition(topic = KafkaConstants.TOPIC_LOGS, partitions = { "0" }) }, groupId = "kafka-consumer", containerFactory = "kafkaListenerContainerFactory")
public void consumerReceive (ConsumerRecord<?, ?> record, Acknowledgment ack){

修改为如下所示的代码。

@KafkaListener(topicPartitions = {@TopicPartition(topic = KafkaConstants.TOPIC_LOGS, partitions = { "0" }) }, groupId = "kafka-consumer-logs", containerFactory = "kafkaListenerContainerFactory")
public void consumerReceive (ConsumerRecord<?, ?> record, Acknowledgment ack){

再次测试,问题解决~~

这次解决的问题真是个奇葩啊!!接下来写个【Kafka系列】专题,详细介绍Kafka的原理、源码解析和实战等内容,小伙伴们你们觉得呢?欢迎文末留言讨论~~

推荐阅读

好了,今天就到这儿吧,我是冰河,大家有啥问题可以在下方留言,也可以加我微信:sun_shine_lyz,我拉你进群,一起交流技术,一起进阶,一起牛逼~~

Kafka又出问题了!的更多相关文章

  1. Kafka vs RocketMQ——多Topic对性能稳定性的影响-转自阿里中间件

    引言 上期我们对比了RocketMQ和Kafka在多Topic场景下,收发消息的对比测试,RocketMQ表现稳定,而Kafka的TPS在64个Topic时可以保持13万,到了128个Topic就跌至 ...

  2. kafka系列教程2(设计构造及原理1)

    kafka采用了一些非主流(unconventional)并经过实践的设计使其高效和可扩展.在实际使用中kafka显示出了相对于常见流行的消息系统的优越性.并且每天能够处理上百GB的新的数据.   类 ...

  3. kafka使用场景

    kafka使用场景 消息 Kafka被当作传统消息中间件的替代品.消息中间件的使用原因有多种(从数据生产者解耦处理,缓存未处理的消息等).与大多数消息系统相比,Kafka具有更好的吞吐量,内置的分区, ...

  4. Kafka入门介绍

    1. Kafka入门介绍 1.1 Apache Kafka是一个分布式的流平台.这到底意味着什么? 我们认为,一个流平台具有三个关键能力: ① 发布和订阅消息.在这方面,它类似一个消息队列或企业消息系 ...

  5. Kafka从入门到进阶

    1.  Apache Kafka是一个分布式流平台 1.1  流平台有三个关键功能: 发布和订阅流记录,类似于一个消息队列或企业消息系统 以一种容错的持久方式存储记录流 在流记录生成的时候就处理它们 ...

  6. 超详细“零”基础kafka入门篇

    1.认识kafka 1.1 kafka简介 Kafka 是一个分布式流媒体平台 kafka官网:http://kafka.apache.org/ (1)流媒体平台有三个关键功能: 发布和订阅记录流,类 ...

  7. 初探kafka

    日常中工作中我并没有对kafka接触很多,但了解到很多的框架都和kafka有着紧密的关系.比如rockmetmq是参考了kafka的设计,neflix的缓存组件ehcache是用kafka做数据的同步 ...

  8. Kafka vs RocketMQ——多Topic对性能稳定性的影响

    引言 上期我们对比了RocketMQ和Kafka在多Topic场景下,收发消息的对比测试,RocketMQ表现稳定,而Kafka的TPS在64个Topic时可以保持13万,到了128个Topic就跌至 ...

  9. 项目17-超详细“零”基础kafka入门篇

    分类: Linux服务篇,Linux架构篇   1.认识kafka 1.1 kafka简介 Kafka 是一个分布式流媒体平台 kafka官网:http://kafka.apache.org/ (1) ...

随机推荐

  1. server sent events

    server sent events server push https://html5doctor.com/server-sent-events/ https://developer.mozilla ...

  2. vue router & query params

    vue router & query params vue router get params from url https://zzk.cnblogs.com/my/s/blogpost-p ...

  3. BGV等 DeFi产品暴涨背后隐藏着什么?

    比特币突破两万七千美金,在此创造了历史.在比特币一路飙升的背后,到底是谁注入了"强心针".笔者认为今年以来推动BTC长期上涨的主要动力主要包括四个:经济形势恶化.央行大量放水(主要 ...

  4. BGV上线17小时最高888.88美金,投资最高回报率近+1778倍, 带动NGK内存暴涨

    至12月3日BGV币上线A网交易所DeFi板块以来,BGV价值飙升长.,据非小号的数据显示,BGV币价是718美元(东八区时间2020年12月4日早上九点四十),相较昨日涨幅达70.14%,以718美 ...

  5. CodeMirror动态修改代码(关键: editor.getDoc().setValue(data); editor.refresh();)

    在使用codemirror时,其原理是根据form中的textarea标签,自动加载其内容,获得代码行的显示.(具体使用方式参见 codemirror官网使用手册 http://codemirror. ...

  6. Typescript快速入门

    目录 什么是Typescript 为什么学习Typescript 快速搭建开发环境 1.安装node.js 2.使用node自带的npm安装Typescript编译器 3.配置vscode编辑环境 4 ...

  7. css选择器,过滤筛选

    $('.required:not(.final_price)').each(function() { if (!$(this).val()) { error_count ++; if ($(this) ...

  8. 用注解开发SpringMVC

    Spring2.5以后,用注解开发SpringMVC的功能十分强大,注解也是SpringMVC的精髓.在实际开发中,都会使用注解来实现. 这让SpringMVC开发工作量最小化,开发者只要专注于业务逻 ...

  9. ajax缺点

    ajax请求在SEO中效率低,SEO就是关键字搜索的匹配度. 比如在百度搜索Java,一般来说内容中出现Java的次数越多排名越靠前,当使用ajax时,它的异步刷新导致必须是页面刷新出来才去刷新数据, ...

  10. JS常用数值验证

    1.正整数验证 正整数是大于0的整数. function validateInteger(val) { return Number.isInteger(val) && val > ...