【JAVA】生产环境kafka重复消费问题记录

【JAVA】生产环境kafka重复消费问题记录

问题描述

业务系统每周都有定时任务在跑,由于是大任务因此采用分而治之思想将其拆分为多个分片小任务采用kafka异步队列消费的形式来减少服务器压力,每个小任务都会调用后台的c++算法,调用完成之后便会回写数据库的成功次数。今天观测到定时任务的分片小任务存在被重复消费的问题,表现就在于数据库中存在多余的成功次数,比如本来大任务分了10个分片,但回写了25次任务处理成功,这是不合理的,因此问题的排查便开始了。

问题排查

一般这种"回写次数"大于"任务总数"的情况,除了代码逻辑问题之外,非常有可能是因为系统出现了重复消费 的问题,多次的消费导致"执行成功回写"这块逻辑被执行了多次。

后台日志确实也显示,同样的消息被消费了多次:

java 复制代码
2024-03-31 03:17:51.926	
[2024-03-31 03:17:51:674 CST] [] [org.springframework.kafka.KafkaListenerEndpointContainer#0-1-C-1] INFO  [] 更新分片状态成功, processId: 16, shardId: 57
2024-03-31 03:17:51.675	
[2024-03-31 03:17:51:662 CST] [] [] to update shard status, processId: 16, shardId: 57


2024-03-31 01:24:10.898	
[2024-03-31 01:24:10:803 CST] [] [] 更新分片状态成功, processId: 16, shardId: 57
2024-03-31 01:24:09.395	
[2024-03-31 01:24:09:302 CST] [] [] to update shard status, processId: 16, shardId: 57

经过后台错误日志排查,发现后台报了许多如下异常:

java 复制代码
[org.apache.kafka.clients.consumer.internals.AbstractCoordinator$HeartbeatResponseHandler.handle(AbstractCoordinator.java:1054)]: 
[Consumer clientId=consumer-consumer_group_prod-6, groupId=consumer_group_prod] 
Attempt to heartbeat failed since group is rebalancing

通过日志可以判断,系统出现了 消费者重平衡 rebalance 的问题,一般消费者组进行重平衡,可能会是以下几种原因:

  • 消费者组引入了新topic
  • 消费者组有成员进组
  • 消费者组有成员离组

由于线上系统周末没有人去新增话题或者新加实例来增加消费者,因此我往 消费者异常离组 这个方向进行排查。因为当消费者离组,并重平衡重新加入消费者组后,之前 未消费的消息又会被拉取一次,符合当前的表现。

此时我查看后台,确实存在消息堆积的现象,同时根据后台c++算法的日志,发现有许多分片在算法中pending了大概两三小时,有的甚至迟迟没跑出结果。并且,我检查了目前系统的kafka配置,其中有两项,就是导致重复消费的元凶:

yaml 复制代码
spring.kafka.consumer.max-poll-interval-ms=7200000
spring.kafka.consumer.max-poll-records=5

这两项配置的含义是,消费者消费间隔最高为7200秒(即2小时),而一次性消费最多会拉取5条消息。

而代码里面的消费逻辑如下:

java 复制代码
// 代码已脱敏处理
for (InputReq inputReq : reqList) {
    boolean shardSuccess = false;

    try {
        // 调用算法A
        algoBiz.callAlgoA(inputReq.getProcessId(), inputReq.getShardId(), inputReq.getProcVersion());

        // 调用算法B
        algoBiz.callAlgoB(inputReq.getProcessId(), inputReq.getShardId(),  inputReq.getProcVersion());

        shardSuccess = true ;
    }
    catch (Exception e) {
        log.error("[] error: {}", e.getMessage());
    }

    // 更新分片状态
    Boolean updateProcShard = inputReq.getToUpdateProcShard();
    algoBiz.updateShardResult(inputReq.getProcessId(), inputReq.getShardId(), updateProcShard, shardSuccess);
}

ack.acknowledge();

代码会消费一次性拉取的最多五条消息之后,才会ack队列协调者,此时如果五条消息处理的总时间超过两小时,那么就会触发消费组重平衡,该消费者重新入队。而结合这周由于后台算法更新,运行时间大大减慢,从而导致了消息重平衡而最终导致了重复消费。

处理方式

由于一时半会儿算法优化还没那么快上线,在调度模块我们就先通过配置进行优化,先解决头疼的重复消费问题。我们先确保程序逻辑超时时间足够长,同时单次消费仅拉取一条消息即可,这样可以大大减少由于算法运行过慢导致的重平衡问题。

java 复制代码
spring.kafka.consumer.max-poll-interval-ms=10800000
spring.kafka.consumer.max-poll-records=1
相关推荐
mjhcsp43 分钟前
C++ 三分查找:在单调与凸函数中高效定位极值的算法
开发语言·c++·算法
我命由我123451 小时前
Element Plus 组件库 - Select 选择器 value 为 index 时的一些问题
开发语言·前端·javascript·vue.js·html·ecmascript·js
沐知全栈开发1 小时前
MySQL 删除数据库指南
开发语言
qq. 28040339842 小时前
js 原型链分析
开发语言·javascript·ecmascript
大飞哥~BigFei2 小时前
RabbitMq消费延迟衰减重试实现思路
java·分布式·rabbitmq
Elnaij2 小时前
从C++开始的编程生活(13)——list和浅谈stack、queue
开发语言·c++
有趣的野鸭2 小时前
JAVA课程十一次实验课程主要知识点示例
java·前端·数据库
q***07144 小时前
Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘(上)
java·spring boot·后端
q***49864 小时前
Spring Boot 3.4 正式发布,结构化日志!
java·spring boot·后端
一晌小贪欢6 小时前
【Python数据分析】数据分析与可视化
开发语言·python·数据分析·数据可视化·数据清洗