3. 数据源接入故障:Kafka 消费延迟、Binlog 同步中断、JDBC 连接池耗尽

数据源接入层,说白了就是实时特征平台的「水源地」。水源出了问题,下游整个管道都得干瞪眼。我排查过不少线上事故,十次里有七八次都跟这三类故障有关:Kafka 消费卡住、Binlog 同步断了、JDBC 连接池被撑爆。咱们一个一个拆开聊。

核心观点:数据源接入故障的根因往往不在数据源本身,而在消费端或中间件的配置细节上。别急着甩锅给网络或数据库。
数据源接入故障排查知识体系 数据源接入故障 Kafka 消费延迟 Rebalance 频繁 消费能力不足 Offset 提交策略 Binlog 同步中断 GTID 不一致 磁盘空间满 网络闪断 JDBC 连接池耗尽 慢查询堆积 连接未释放 最大连接数过小 排查三板斧:监控告警 → 日志定位 → 配置调优

3.1 Kafka 消费延迟:不只是「慢」那么简单

Kafka 消费延迟,最直观的表现就是消息积压。你去看 Consumer Lag,发现已经几百万条了。这时候别慌,先问自己三个问题:是生产端写太快?还是消费端处理太慢?还是中间出了什么幺蛾子?

我个人习惯先查 Consumer Group 的状态。用 kafka-consumer-groups.sh --describe 看一眼,如果发现分区分配不均匀,或者有成员频繁加入退出,那大概率是 Rebalance 在作祟。

排查技巧:用 kafka-consumer-groups --describe --group your_group --members --verbose 看每个消费者的分区分配情况。如果某个消费者分配了 10 个分区,另一个只分了 1 个,明显不均衡。

我在项目中遇到过一回,线上 Kafka 消费延迟从 10 秒飙到了 15 分钟。查了半天,发现是某个同事把 max.poll.records 设成了 5000,但单条消息处理时间平均要 200ms。你算算,5000 × 200ms = 1000 秒,一次 poll 就要处理 16 分钟,不延迟才怪。

常见的 Kafka 消费延迟根因,我整理了一张表:

现象 可能原因 快速定位命令
Lag 持续增长 消费能力不足 / 分区数过少 kafka-consumer-groups --describe
Lag 忽高忽低 Rebalance 频繁 / GC 停顿 查看消费者日志中的 rebalance 事件
部分分区 Lag 高 分区分配不均 / 某分区数据倾斜 --members --verbose 查看分配
消费端报错 Offset 提交失败 / 序列化异常 查看 commit offset failed 日志
避坑指南:我曾经把 session.timeout.ms 设得太小(比如 3 秒),结果消费端稍微 Full GC 一下就被踢出组,触发 Rebalance。Rebalance 期间所有分区都停止消费,Lag 瞬间暴涨。建议生产环境至少设到 30 秒以上。

3.2 Binlog 同步中断:数据管道「断流」了

Binlog 同步,说白了就是实时特征平台从数据库「偷」数据的方式。一旦中断,下游特征全部停摆。我见过最惨的一次,Binlog 断了 6 小时没人发现,恢复后补数据补了整整一天。

为什么会中断?原因其实很固定:

  • GTID 不一致:主从切换后,Canal 或 Debezium 记录的 GTID 和新的主库对不上,直接罢工。
  • 磁盘空间满:Binlog 文件把磁盘撑爆了,MySQL 直接拒绝写入,同步自然断。
  • 网络闪断:Canal 和 MySQL 之间的长连接断了,重连机制没生效。
  • Binlog 文件被清理:expire_logs_days 设得太短,Canal 还没消费完就被删了。

我个人习惯在 Canal 的日志里搜 EOFException 或 SocketException,这两个关键字基本能覆盖 80% 的中断场景。另外,记得监控 Canal 的 delay 指标,一旦超过阈值立刻告警。

关键配置:MySQL 的 binlog_expire_logs_seconds 建议设为 604800(7 天),Canal 的 canal.instance.master.journal.name 和 canal.instance.master.position 要定期备份,防止重启后找不到位置。

嗯,这里要注意一个细节:Binlog 同步中断后,恢复时一定要确认 GTID 或 Position 的连续性。我曾经跳过这个步骤,结果数据出现了重复和缺失,排查了整整两天才发现是 GTID 回滚导致的。

3.3 JDBC 连接池耗尽:无声的「雪崩」

JDBC 连接池耗尽,是实时特征平台里最隐蔽的故障之一。表面上看,业务接口响应变慢,数据库 CPU 正常,但就是拿不到连接。你想想看,连接池里的连接都被占着不放,新的请求只能排队等超时。

我遇到过最典型的一次:某个实时特征计算任务,每次处理都要查一次 MySQL,但查询特别慢(3 秒以上)。并发一上来,20 个连接全被占满,后续请求全部超时。更坑的是,超时后连接没有正确归还,直接泄漏了。

排查 JDBC 连接池问题,我一般按这个顺序来:

  1. 看监控:连接池的 active 数是否等于 maxActive?waiting 数是否持续增长?
  2. 查慢查询:MySQL 的 slow_query_log 里有没有长时间未返回的 SQL?
  3. 验代码:检查 try-with-resources 或 finally 块是否确保连接归还。
  4. 调参数:maxActive、maxWait、removeAbandonedTimeout 是否合理?
推荐配置:HikariCP 连接池,maximumPoolSize 建议设为 CPU核心数 × 2 + 1,connectionTimeout 设为 3000ms,idleTimeout 设为 600000ms。别贪多,连接数不是越大越好,太多反而增加数据库负担。

我曾经踩过一个坑:某个实时任务用了 Druid 连接池,removeAbandonedTimeout 设成了 60 秒,但业务 SQL 执行时间偶尔会超过 60 秒。结果连接被 Druid 强制回收,但业务线程还在用,直接报了 Connection is closed 异常。后来我把超时时间改成了 300 秒,同时优化了 SQL 性能,问题才彻底解决。

避坑指南:连接池耗尽往往不是连接数不够,而是连接被「占着茅坑不拉屎」。优先排查慢查询和连接泄漏,别一上来就加 maxActive。加连接数只是治标,治本要优化 SQL 和代码。

3.4 故障排查的「三板斧」

说了这么多,总结一下我自己的排查套路。遇到数据源接入故障,别慌,按这三步走:

  • 第一板斧:看监控。Kafka Lag、Binlog Delay、连接池 Active 数,这三个指标能告诉你「哪里疼」。
  • 第二板斧:查日志。消费端日志、Canal 日志、应用日志,搜关键字 error、exception、timeout、rebalance。
  • 第三板斧:调配置。根据定位到的根因,调整消费参数、连接池参数、同步参数。每次只改一个变量,观察效果。

说白了,数据源接入故障不可怕,可怕的是没有排查思路。你只要把监控搭好、日志收全、配置参数理解透,大部分问题都能在 10 分钟内定位。嗯,剩下的 10% 嘛……那就得靠经验和运气了。