kafka消费延迟或者重复消费原因

简介

由于项目中需要使用kafka作为消息队列,并且项目是基于spring-boot来进行构建的,所以项目采用了spring-kafka作为原生kafka的一个扩展库进行使用。先说明一下版本:

  • spring-boot 的版本是1.4.0.RELEASE
  • kafka 的版本是0.9.0.x 版本
  • spring-kafka 的版本是1.0.3.RELEASE

用过kafka的人都知道,对于使用kafka来说,producer的使用相对简单一些,只需要把数据按照指定的格式发送给kafka中某一个topic就可以了。本文主要是针对spring-kafka的consumer端上的使用进行简单一些分析和总结。

kafka的速度是很快,所以一般来说producer的生产消息的逻辑速度都会比consumer的消费消息的逻辑速度快。

具体案例

之前在项目中遇到了一个案例是,consumer消费一条数据平均需要200ms的时间,并且在某个时刻,producer会在短时间内产生大量的数据丢进kafka的broker里面(假设平均1s中内丢入了5w条需要消费的消息,这个情况会持续几分钟)。

对于这种情况,kafka的consumer的行为会是:

  • kafka的consumer会从broker里面取出一批数据,�给消费线程进行消费。
  • 由于取出的一批消息数量太大,consumer在session.timeout.ms时间之内没有消费完成
  • consumer coordinator 会由于没有接受到心跳而挂掉,并且出现一些日志
    日志的意思大概是coordinator挂掉了,然后自动提交offset失败,然后重新分配partition给客户端
  • 由于自动提交offset失败,导致重新分配了partition的客户端又重新消费之前的一批数据
  • 接着consumer重新消费,又出现了消费超时,无限循环下去。

解决方案

遇到了这个问题之后, 我们做了一些步骤:

  • 提高了partition的数量,从而提高了consumer的并行能力,从而提高数据的消费能力
  • 对于单partition的消费线程,增加了一个固定长度的阻塞队列和工作线程池进一步提高并行消费的能力
  • 由于使用了spring-kafka,则把kafka-client的enable.auto.commit设置成了false,表示禁止kafka-client自动提交offset,因为就是之前的自动提交失败,导致offset永远没更新,从而转向使用spring-kafka的offset提交机制。并且spring-kafka提供了多种提交策略:
    这些策略保证了在一批消息没有完成消费的情况下,也能提交offset,从而避免了完全提交不上而导致永远重复消费的问题。

分析

那么问题来了,为什么spring-kafka的提交offset的策略能够解决spring-kafka的auto-commit的带来的重复消费的问题呢?下面通过分析spring-kafka的关键源码来解析这个问题。

首先来看看spring-kafka的消费线程逻辑

  • 上面可以看到,如果auto.commit关掉的话,spring-kafka会启动一个invoker,这个invoker的目的就是启动一个线程去消费数据,他消费的数据不是直接从kafka里面直接取的,那么他消费的数据从哪里来呢?他是从一个spring-kafka自己创建的阻塞队列里面取的。

  • 然后会进入一个循环,从源代码中可以看到如果auto.commit被关掉的话, 他会先把之前处理过的数据先进行提交offset,然后再去从kafka里面取数据。

  • 然后把取到的数据丢给上面提到的阻塞列队,由上面创建的线程去消费,并且如果阻塞队列满了导致取到的数据塞不进去的话,spring-kafka会调用kafka的pause方法,则consumer会停止从kafka里面继续再拿数据。

  • 接着spring-kafka还会处理一些异常的情况,比如失败之后是不是需要commit offset这样的逻辑。

方法二

  • 可以根据消费者的消费速度对session.timeout.ms的时间进行设置,适当延长
  • 或者减少每次从partition里面捞取的数据分片的大小,提高消费者的消费速度。

参考链接:https://www.jianshu.com/p/4e00dff97f39

 

 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的 C++ Kafka 延迟消费消息的示例代码: ```c++ #include <iostream> #include <librdkafka/rdkafkacpp.h> int main() { std::string brokers = "localhost:9092"; std::string topic = "test_topic"; std::string group_id = "test_group"; RdKafka::Conf *conf = RdKafka::Conf::create(RdKafka::Conf::CONF_GLOBAL); std::string errstr; // 设置 Kafka brokers if (conf->set("bootstrap.servers", brokers, errstr) != RdKafka::Conf::CONF_OK) { std::cerr << "Failed to set brokers: " << errstr << std::endl; return 1; } // 设置消费者组 if (conf->set("group.id", group_id, errstr) != RdKafka::Conf::CONF_OK) { std::cerr << "Failed to set group id: " << errstr << std::endl; return 1; } // 设置消费者自动提交偏移量 if (conf->set("enable.auto.commit", "true", errstr) != RdKafka::Conf::CONF_OK) { std::cerr << "Failed to set auto commit: " << errstr << std::endl; return 1; } // 创建消费者对象 RdKafka::KafkaConsumer *consumer = RdKafka::KafkaConsumer::create(conf, errstr); if (!consumer) { std::cerr << "Failed to create consumer: " << errstr << std::endl; return 1; } // 订阅主题 std::vector<std::string> topics = {topic}; if (consumer->subscribe(topics, errstr) != RdKafka::ERR_NO_ERROR) { std::cerr << "Failed to subscribe to topic " << topic << ": " << errstr << std::endl; return 1; } while (true) { // 从 Kafka 获取消息 RdKafka::Message *msg = consumer->consume(1000); if (!msg) { continue; } // 检查消息是否有效 if (msg->err() != RdKafka::ERR_NO_ERROR) { std::cerr << "Failed to consume message: " << msg->errstr() << std::endl; continue; } // 在这里添加延迟逻辑 std::cout << "Received message: " << std::string((char *)msg->payload()) << std::endl; // 手动提交偏移量 consumer->commitSync(msg); } return 0; } ``` 在上面的代码中,我们首先创建了一个 Kafka 消费者对象,并设置了 Kafka brokers、消费者组、自动提交偏移量等参数。然后我们订阅了一个主题,并在一个循环中持续从 Kafka 获取消息。在获取到消息后,我们可以在这里添加延迟逻辑,比如等待一段时间再处理消息。最后,我们手动提交偏移量,以确保消息不会被重复消费。 需要注意的是,上面的代码只是一个简单的示例,实际使用中还需要考虑更多的因素,比如异常处理、多线程处理等。同时,延迟消费消息也可能会对系统的实时性产生影响,需要根据实际需求进行权衡。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值