Kafka消费端周期性停顿,导致工厂设备甘特图失准

一、业务背景:设备状态与甘特图的生死时速

在工厂的 IoT 场景中,我们实时采集设备的运行状态(运行、停机、故障等)。数据流向如下:

设备传感器 -> Kafka(原始状态) -> Spring Boot 实时计算 -> Kafka(明细数据) -> Spring Boot 聚合写入 -> Doris

核心业务逻辑

我们需要精确计算设备在每种状态下的持续时间,用于生成生产报表和设备利用率甘特图。

  1. 实时流处理 :消费设备状态变更数据,计算 状态开始时间状态结束时间的差值。

  2. 兜底定时任务 :由于网络抖动或设备故障,可能存在"状态丢了"的情况。因此我们设定了一个 10分钟定时任务

    • 扫描明细表,如果某台设备超过10分钟没有新的状态数据流入。

    • 自动插入一条"停机状态"数据,以确保甘特图不会显示设备一直在运行。

问题爆发

业务人员反馈:甘特图上显示的停机时间比实际长很多,甚至出现了连续的"幽灵停机"。


二、排查过程:从业务到技术的"鬼打墙"

第一阶段:怀疑数据计算逻辑

起初以为是定时任务写错了,导致误判设备停机。

  • 排查 :检查定时任务的SQL逻辑,确认 NOW() - last_update_time > 10分钟的条件无误。

  • 结果:逻辑正确,但数据入库的时间戳确实"断档"了。

第二阶段:怀疑Kafka(经典的"甩锅"环节)

既然数据断了,肯定是消息没消费。

  • 现象:Kafka消费者每隔3分钟就停止消费,Lag开始堆积,1分钟后又恢复,周而复始。

  • 操作:疯狂调整Kafka参数。

java 复制代码
spring:
  kafka:
    consumer:
      properties:
        session.timeout.ms: 45000
        max.poll.interval.ms: 300000
        heartbeat.interval.ms: 3000
  • 结果:无效。消费依然规律性地"睡一分钟"。

第三阶段:怀疑Zookeeper

难道是ZK选举导致协调器漂移?

  • 排查 :检查ZK日志,grep -c "LEADER ELECTION" zookeeper.out,结果是0。

  • 结论:ZK非常稳定,排除。

第四阶段:怀疑GC和业务线程

是不是Full GC把线程卡住了?

  • 排查jstat -gcutil pid 1000,GC正常,没有长时间的Stop The World。

  • 现象 :通过 jstack抓取线程栈,发现消费者线程大多处于 WAITING (parking)状态,且卡在 httpclient相关代码处。

第四阶段:怀疑GC和业务线程

是不是Full GC把线程卡住了?

  • 排查jstat -gcutil pid 1000,GC正常,没有长时间的Stop The World。

  • 现象 :通过 jstack抓取线程栈,发现消费者线程大多处于 WAITING (parking)状态,且卡在 httpclient相关代码处。


三、根因定位:被遗忘的HttpClient连接池

我们的架构中,Spring Boot消费Kafka数据后,需要通过 Stream Load ​ 方式写入 Doris。这里使用了 Apache CloseableHttpClient

问题代码逻辑

java 复制代码
@KafkaListener(topics = "device-status")
public void consume(DeviceStatus status) {
    // 1. 计算状态持续时间
    calculateDuration(status);
    
    // 2. 写入Doris
    httpClient.execute(streamLoadRequest); // 阻塞点!
}

致命陷阱:HttpClient默认连接池配置

  • maxTotal: 20 (整个连接池最多20个连接)

  • defaultMaxPerRoute: 2 (每个Doris FE节点最多2个连接)

在高并发设备数据涌入时发生了什么?

  1. 连接耗尽:设备数多,并发高。10个消费者线程同时写入Doris,很快占满了连接池(20个连接)。

  2. 线程阻塞 :第21个请求进来,httpClient.execute()开始阻塞,等待获取连接。

  3. 心跳超时:由于消费者线程被阻塞,无法向Kafka Broker发送心跳(Heartbeat)。

  4. 重平衡(Rebalance):Broker认为消费者死掉了,踢出消费者组,触发Rebalance。

  5. 消费暂停:Rebalance期间,分区被回收,消费完全停止。这就是为什么业务看到"数据断档"。

  6. 定时任务误判 :因为消费停止,10分钟定时任务扫描时发现"设备10分钟没数据",错误地插入了一条停机数据

  7. 恢复:几秒后,Doris写入完成,连接释放,消费者重新加入组,继续消费。

这就完美解释了 "消费一会停一会" ​ 以及 **"甘特图停机时间不准"**​ 的现象。


四、解决方案:打破连接瓶颈

1. 紧急止血:调大连接池

既然20个不够,那就先调大。

java 复制代码
@Configuration
public class HttpClientConfig {

    @Bean
    public CloseableHttpClient httpClient() {
        PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
        // 根据消费者并发数 * 2 调整
        connManager.setMaxTotal(200); 
        // 根据Doris FE节点数调整
        connManager.setDefaultMaxPerRoute(50); 

        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(5000)
                .setSocketTimeout(30000)
                .setConnectionRequestTimeout(2000) // 获取连接超时,避免无限期等
                .build();

        return HttpClients.custom()
                .setConnectionManager(connManager)
                .setDefaultRequestConfig(requestConfig)
                .build();
    }
}

调整效果:消费立刻恢复平滑,不再停顿。

2. 业务修复:校准甘特图

数据连续后,定时任务的误判消失。对于历史脏数据,我们通过离线任务进行了订正。


五、经验总结与避坑指南

1. 日志会骗人,线程栈不说谎

Kafka日志显示 Group coordinator is unavailableRebalance,这只是结果 ,不是原因 。当遇到这种周期性停顿时,第一时间 jstack看线程在干嘛,往往能直接定位到阻塞点(数据库、锁、HTTP连接)。

2. AI是向导,不是司机

在排查过程中,AI助手给出了很多方向(调Kafka参数、检查ZK等),这在初期很有帮助。但是,具体是哪一行代码、哪一个配置导致的,必须开发者亲自下场,结合业务逻辑去测试、调试。AI无法替你跑生产环境的流量。

3. 高并发下,中间件的默认值往往是"坑"

Apache HttpClient 的默认最大连接数(20)和单路由连接数(2)是为"温和"的场景设计的。在微服务和高并发数据管道中,这绝对不够用。凡是涉及连接池(DB连接池、Redis连接池、HTTP连接池),上线前必须根据并发量重新评估。

4. 业务闭环验证

技术问题的解决不能止步于日志不报错。就像这次,必须回到业务端(甘特图)确认数据准确,才算真正的完结。


希望这篇复盘能帮助大家在遇到"间歇性消费停顿"时,绕过Kafka的迷雾,直击连接池的本质。

相关推荐
江畔何人初2 小时前
Kafka 消息队列概念及与RabbitMQ 的区别
运维·服务器·分布式·云原生·kafka·rabbitmq
indexsunny1 天前
互联网大厂Java求职面试实战:Spring Boot微服务在电商场景中的应用与挑战
java·spring boot·redis·面试·kafka·oauth2·microservices
百结2141 天前
zookeeper+kafka消息队列群集部署
分布式·zookeeper·kafka
白露与泡影1 天前
从零学习Kafka:ZooKeeper vs KRaft
学习·zookeeper·kafka
Devin~Y1 天前
大厂 Java 面试实战:Spring Boot 微服务 + Redis 缓存 + Kafka 消息 + Kubernetes + RAG(小Y水货翻车记)
java·spring boot·redis·kafka·spring security·jwt·oauth2
indexsunny2 天前
互联网大厂Java面试实录:微服务+Spring Boot在电商场景中的应用
java·spring boot·redis·微服务·eureka·kafka·spring security
Jackyzhe2 天前
从零学习Kafka:ZooKeeper vs KRaft
学习·zookeeper·kafka
工作log2 天前
从零搭建 ELK + Kafka 日志收集系统(Spring Boot + Logback 直连 Kafka)
spring boot·elk·kafka
QC·Rex2 天前
消息队列架构设计 - Kafka/RocketMQ/RabbitMQ 深度对比与实战
kafka·rabbitmq·rocketmq