一步步解决spring-kafka消息丢失

本文深入探讨Kafka消费不稳定的疑难杂症,从环境差异、版本兼容性、消费组冲突到源码解析,逐步揭秘Kafka消费机制,提供实用的故障定位与解决策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

kafka的高性能、流式响应、对大数据支持是有目共睹的,在我们内部项目product-search有使用CDC的解决方案,而kafka成为该方案的输出源,此时各个业务方只需要去订阅消费即可。

1.  问题描述

有一天有开发人员找到我:我的consumer端消费kafka为何有时候可以消费到数据,有时候不行?dev环境我自己试了是没有问题的,而test环境就不行,不行的时候系统也没有任何报错信息,why?

2. 分析症状

说实话,每当遇到这样的问题(连个error都没有),内心是相当的囧,能怎么办?只能立刻马上冷静下来,然后去现场寻找蛛丝马迹。尴尬的时刻到了,了解下来故障现场确实没有留下什么迹象!但还是有些可疑点

3. 去伪求真

其实就是排除可疑点,巧合的事情就是最大的可能性。接下来就要按套路来解决问题:

1. kafka server是否真的数据?

很容易确认(借助一些kafka监控),基本可以排除

2. kafka client版本问题,使用方法问题,导致无法消费?

Spring for Apache Kafka Version

Spring Integration for Apache Kafka Version

kafka-clients

2.2.x

3.1.x

2.0.0, 2.1.0

2.1.x

3.0.x

1.0.x, 1.1.x, 2.0.0

2.0.x

3.0.x

0.11.0.x, 1.0.x

1.3.x

2.3.x

0.11.0.x, 1.0.x

1.2.x

2.2.x

0.10.2.x

1.1.x

2.1.x

0.10.0.x, 0.10.1.x

1.0.x

2.0.x

0.9.x.x

N/A*

1.3.x

0.8.2.2

<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
    <version>1.1.1.RELEASE</version>
</dependency>

application.yml(见下面)
spring:
    profiles: default
    kafka:
      bootstrap-servers: 192.168.1.183:9092,192.168.1.184:9092,192.168.1.182:9092
      consumer:
        group-id: opa-consumer-group
        auto-offset-reset: earliest
        enable-auto-commit: false
        auto-commit-interval: 100

@Component
public class KafkaReceiver {

    private static final String CDC_TOPIC = "BEEHIVE_XKDATA";
    private static final Logger LOGGER = LoggerFactory.getLogger(KafkaReceiver.class);

    @KafkaListener(topics = {CDC_TOPIC})
    public boolean consumer(ConsumerRecord<?, ?> record) {
        Optional<?> kafkaMessage = Optional.ofNullable(record.value());
        if (kafkaMessage.isPresent()) {
            Object message = kafkaMessage.get();
            //todo。。。略
        }
        return true;
    }
}

首先,版本是没有问题的,kafka driver可以直接去官方网站去确认,

用法(配置和代码),也是官方Samples,我擦这么简单的不可能有问题,基本上此项也可以排除。

 3. kafka client可能被别的consumer消费掉?

这个无法识别,需要对源码有一定了解的情况下才可以确认,懂源码不是让你改源码,更多是是让你埋日志(如果它有debug日志就打开,如果不够就把源码拉下来放自己项目中,加日志)。

<logger name="org.springframework.kafka.listener.adapter.RecordMessagingMessageListenerAdapter" level="DEBUG"/> 
<logger name="org.springframework.messaging.handler.invocation.InvocableHandlerMethod" level="trace"/>

基本上已经可以确认有问题!我发现:被bean A消费了,这个bean A(它继承了KafkaReceiver),bean A被别的类库扫描到了spring ioc中去了,那这又会有什么问题呢?后续源码会讲到,它不能这么做

4. 读源码

1. 要对kafka-driver有一个基本的认识,它是poll模型

2. 对spring-boot有一定了解的都知道,入口在spring.factories(spring-boot-autoconfigure),找到启动类org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\

3.看代码

KafkaProperties配置类
KafkaTemplate生产者生产消息用的,主要有send()
ProducerListener生产者监听接口,主要有onSuccess()、onError()。默认实现LoggingProducerListener,它打印错误消息
ConsumerFactory主要是createKafkaConsumer()
ProducerFactory主要是createProducer()
ConcurrentKafkaListenerContainerFactoryConfigurer配置ContainerProperties(包含了一个监听容器的运行时配置信息,主要定义了监听的主题、分区、初始化偏移量,还有消息监听器)
ConcurrentKafkaListenerContainerFactory创建createContainerInstance
KafkaBootstrapConfigurationKafkaListenerAnnotationBeanPostProcessor:处理各种注解@KafkaListener,它是核心,创建都是它驱动的
KafkaListenerEndpointRegistry:它实现SmartLifecycle(start、stop),掌管MessageListenerContainer的start和stop

4.1 MessageListener接口

实现onMessage方法,去消费接收到的消息。两种方案:

  • MessageListener 消费完消息后自动提交offset(enable.auto.commit=true时),可提高效率,存在消费失败但移动了偏移量的风险。
  • AcknowledgingMessageListener 消费完消息后手动提交offset(enable.auto.commit=false时)效率降低,无消费失败但移动偏移量的风险

MessagingMessageListenerAdapter的实现有:RecordMessagingMessageListenerAdapter、BatchMessagingMessageListenerAdapter,他们是由ConcurrentKafkaListenerContainerFactory创建的。

4.2 ConcurrentMessageListenerContainer核心类

它继承自抽象类AbstractMessageListenerContainer,核心方法doStart()可以创建多个consumer(KafkaMessageListenerContainer),实例化方式ConcurrentMessageListenerContainer(ConsumerFactory<K, V> consumerFactory,ContainerProperties containerProperties)。

4.3 执行流程

  1. 容器启动,KafkaListenerAnnotationBeanPostProcessor开始扫描并创建consumer和producter;
  2. kafkaConsumer拉取消息,首先Fetcher(请求获取器)获取请求并存储在unset中,ConsumerNetworkClient网络客户端执行poll(),调用NetWlrikClient的send()方法从unset中获取ClientRequest请求转成RequestSend最终塞进Selector的KafkaChannel通道中,Seletcor.send()从kafka集群拉取待消费数据ConsumerRecords。通过recordsToProcess(LinkedBlockingQueue)实现消息的的存储。
  3. 消费者监听器MessageListener.onMessage()执行用户自定义的实际消费业务逻辑。

4.4 KafkaMessageListenerContainer

上面讲过,每个consumer都有一个这样的实例,核心方法是start(),其实KafkaMessageListenerContainer通过内部类ListenerConsumer和提交监听消费者任务。

1.定义消费者订阅topic或者指定分区
2.设置监听器,支持4种:
  1)BatchAcknowledgingMessageListener批量需确认消息监听器
  2)AcknowledgingMessageListener需确认消息监听器
  3)BatchMessageListener批量消息监听器
  4)MessageListener消息监听器(用的最多,一次消费一条)

提交监听消费者任务(ListenerConsumer),返回Future并赋值。这里我们看一下任务Runnable接口的run方法,分两种情况:
1.如果自定义了分区,没必要再平衡分配分区了,直接回调
2.未指定分区,进入自旋消费

ListenerInvoker.startInvoker()触发onMessage(),

1.如果用户自定义了分区且非自动提交,那么开启异步线程执行ListenerInvoker任务。
2.未指定分区,进入自旋
1)拉取消费记录
ConsumerRecords<K, V> records = this.consumer.poll(this.containerProperties.getPollTimeout());
2)如果设置了自动提交,直接在当前线程执行
invokeListener(records);
3)否则发送消息进缓存队列
sendToListener(records)

<think>好的,我现在需要帮助用户排查Kafka消费者未能及时poll到新数据的问题。首先,我得回忆一下常见的导致这个问题的原因。用户提到的是消费者没有及时收到新生产的数据,可能涉及配置、偏移量设置、消费者组状态等多个方面。 首先,消费者的配置。用户可能设置了自动提交偏移量,如果自动提交间隔太长,或者消费者在处理完消息后没有正确提交偏移量,可能导致重复消费或者无法获取新消息。另外,消费者的poll间隔时间也可能影响,如果两次poll之间的时间过长,可能会延迟获取数据。此外,消费者可能配置了`fetch.min.bytes`或`fetch.max.wait.ms`,如果设置不合理,可能会让消费者等待更长时间才拉取数据。 然后是消费者的组管理。用户提到引用的内容中,如果消费者绑定指定了group,那么`startOffset`会被设置为`earliest`,否则为`latest`。这里可能存在两种情况:如果消费者组是新创建的且没有指定group,可能会默认从最新的偏移量开始消费,从而错过之前的数据。或者,如果消费者组之前已经消费过,但偏移量没有重置,可能导致无法获取新数据。此外,消费者是否正确地加入了一个消费者组,或者可能处于独立消费者的状态,这也会影响偏移量的提交和数据的拉取。 接下来是生产者的确认机制。生产者可能没有正确配置acks,导致消息没有被成功写入Kafka。比如,如果acks设为0,生产者不会等待确认,可能导致消息丢失。另外,消息可能没有被发送到正确的主题或分区,或者存在网络问题导致生产者无法连接Kafka。 还需要考虑分区分配的问题。消费者可能被分配到了没有新消息的分区,或者分区的leader发生变更,导致消费者无法及时获取数据。此外,消费者可能因为心跳问题被踢出消费者组,导致无法继续消费。 其他可能性包括Kafka服务端的延迟,比如副本同步延迟,或者消费者客户端的版本与Kafka服务端不兼容,导致某些功能异常。此外,消费者的过滤器设置可能意外地过滤掉了新消息,或者存在反序列化错误导致消息被跳过。 接下来,我需要将这些可能性整理成一个结构化的排查步骤,帮助用户一步步检查。可能需要先检查消费者配置,尤其是偏移量设置和自动提交,然后确认消费者组的状态和偏移量位置,接着检查生产者的配置和消息确认情况,最后查看分区分配和网络状况等。 在引用部分,用户提供的引用内容提到当显式设置消费者组时,`startOffset`是`earliest`,否则匿名消费者组使用`latest`。这可能意味着如果用户没有显式设置group,新的消费者组会从最新的偏移量开始消费,从而错过之前已提交的消息。如果用户希望消费者能够读取到新产生的数据,可能需要检查group的配置是否正确,或者考虑重置偏移量到最新位置。 最后,生成相关问题时要覆盖配置、偏移量管理、生产者问题、网络因素等方面,确保用户能全面了解可能的原因和解决方法。</think>### Kafka消费者未及时poll数据的排查步骤 #### 1. 检查消费者配置参数 - **auto.offset.reset** 若消费者组首次启动或偏移量失效时,该参数决定起始位置。若设置为`latest`(默认值),消费者会忽略已存在的消息[^1] *验证方式*:查看配置`spring.cloud.stream.kafka.bindings.<channel>.consumer.startOffset`是否被覆盖 - **enable.auto.commit** 自动提交偏移量可能导致消费延迟(默认间隔5000ms),若处理速度慢于提交间隔,会重复消费旧数据 *临时方案*:手动提交偏移量并缩短提交周期 - **fetch.min.bytes/fetch.max.wait.ms** 过大的`fetch.min.bytes`(默认1字节)或过长的`fetch.max.wait.ms`(默认500ms)会延长poll间隔 *调整示例*: ```java spring.kafka.consumer.fetch-min-bytes=1 spring.kafka.consumer.fetch-max-wait=100 ``` #### 2. 验证消费者组状态 - 使用命令查看消费者组偏移量: ```bash ./kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group <group_id> ``` 观察`CURRENT-OFFSET`与`LOG-END-OFFSET`的差值,若差值持续增长说明未消费新数据 - 当消费者使用匿名组(未显式指定group)时,每次重启会创建新组并从`latest`开始消费,可能导致丢失历史消息 #### 3. 生产者端验证 - **消息确认机制** 检查生产者配置`acks=all`(确保消息写入所有副本)和`retries>0` *典型问题*:网络抖动导致生产者未收到写入成功的ACK - **跨分区验证** 使用控制台消费者检查特定分区: ```bash ./kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic <topic> --partition 0 --from-beginning ``` #### 4. 网络/服务端问题排查 - **消费者心跳超时** 检查`session.timeout.ms`(默认10秒)和`heartbeat.interval.ms`(默认3秒) *异常表现*:消费者频繁重平衡 - **Broker ISR状态** 验证分区副本同步状态: ```bash ./kafka-topics.sh --describe --bootstrap-server localhost:9092 --topic <topic> ``` 若Leader的`Isr`集合数量小于配置的`min.insync.replicas`,生产者会收到异常 #### 5. 客户端调试技巧 启用DEBUG日志查看poll细节: ```properties logging.level.org.apache.kafka.clients.consumer=DEBUG ``` 典型日志线索: ``` DEBUG o.a.k.c.consumer.KafkaConsumer - [Consumer clientId=...] Polled 0 records DEBUG o.a.k.c.c.internals.Fetcher - Sending fetch for partitions [...] ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值