Canal Adapter连接泄漏问题分析与解决方案 1. 问题现象与初步诊断那天凌晨3点监控系统突然告警Canal Adapter的同步延迟指标突破警戒线。登录服务器查看日志发现最后一条同步记录停留在2小时前但奇怪的是Canal Adapter进程仍然在运行没有崩溃退出。这种静默失效的情况最让人头疼——系统没有完全挂掉但核心功能已经停止工作。通过以下命令检查各组件状态# 检查Canal Server状态 ps aux | grep canal.deployer netstat -tlnp | grep 11111 # 检查Adapter日志尾部的异常 tail -n 100 /opt/canal/adapter/logs/adapter.log # 验证MySQL binlog位置是否更新 mysql -h mysql_host -u canal -p -e SHOW MASTER STATUS日志中出现了几个关键线索持续报错Get canal batch data failed但未终止进程最后一次成功同步的binlog位置是mysql-bin.000023:47219MySQL端show master status显示当前binlog位置已是mysql-bin.000025:11234这表明Adapter仍在运行但已经无法从Canal Server获取新的binlog事件。更诡异的是Canal Server本身的日志显示它仍在正常接收MySQL的binlog。2. 网络连接与线程阻塞分析首先怀疑是网络问题使用tcpdump抓取Canal Adapter与Server之间的通信tcpdump -i eth0 -w canal.pcap host adapter_ip and port 11111分析抓包文件发现Adapter每隔30秒会发送心跳包但最近2小时没有发起任何数据请求。这指向两种可能Adapter内部的工作线程阻塞数据解析环节出现死锁通过jstack查看Java线程堆栈jstack -l $(pgrep -f canal.adapter) thread_dump.txt在thread_dump.txt中发现关键线索pool-2-thread-1 #17 prio5 os_prio0 tid0x00007f48740f6800 nid0x5cf3 waiting on condition [0x00007f486b7e7000] java.lang.Thread.State: TIMED_WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for 0x00000000f5d9a2c8 (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078) at com.alibaba.druid.pool.DruidDataSource.takeLast(DruidDataSource.java:2185) at com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource.java:1665) at com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:1403) at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1383) at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1373)关键发现线程池中的工作线程全部阻塞在获取数据库连接这一步Druid连接池的所有连接都被占用且没有释放。这解释了为什么同步停止但进程仍在——所有工作线程都在等待永远不会释放的数据库连接。3. 连接泄漏的根因定位检查Adapter的Druid连接池配置conf/application.ymlcanal.conf: srcDataSources: defaultDS: url: jdbc:mysql://mysql_host:3306/db username: canal password: xxxx initialSize: 5 maxActive: 20 maxWait: 60000 minIdle: 5配置看似正常但结合代码分析发现当ES写入失败时Adapter会重试但未正确关闭对应的数据库连接。通过以下步骤验证在故障时间点附近ES集群曾出现过短暂不可用从ES监控数据确认Adapter的重试逻辑中每次重试都会获取新连接但失败时未释放持续的重试导致连接池耗尽使用Arthas观察连接池状态验证这一点[arthas12345]$ watch com.alibaba.druid.pool.DruidDataSource getActiveCount returnObj4. 解决方案与验证临时解决方案是重启Adapter释放所有连接./bin/stop.sh sleep 5 ./bin/startup.sh但根本解决方案需要修改Adapter代码在ES写入失败时确保连接释放。我们在本地构建了修复版本主要改动点在ES写入逻辑中添加try-with-resources确保连接关闭try (Connection conn dataSource.getConnection()) { // 执行查询和ES写入 } catch (Exception e) { metrics.markFailed(); throw new RuntimeException(e); }配置Druid连接泄漏检测defaultDS: removeAbandoned: true removeAbandonedTimeout: 300 logAbandoned: true添加重试次数限制和退避策略RetryTemplate retryTemplate new RetryTemplate(); ExponentialBackOffPolicy backOffPolicy new ExponentialBackOffPolicy(); backOffPolicy.setInitialInterval(1000); backOffPolicy.setMultiplier(2); backOffPolicy.setMaxInterval(10000); retryTemplate.setBackOffPolicy(backOffPolicy); retryTemplate.setRetryPolicy(new SimpleRetryPolicy(3));5. 防御性编程实践基于这次教训我们实施了以下改进措施监控增强实时监控Druid连接池活跃连接数设置ES写入延迟告警阈值30秒触发定期验证binlog位置是否持续更新运维脚本自动化#!/bin/bash # 检查同步延迟 DELAY$(canal_metrics.sh get_delay) if [ $DELAY -gt 300 ]; then ./bin/stop.sh sleep 5 ./bin/startup.sh send_alert Canal Adapter restarted due to $DELAY seconds delay fi定期维护流程每周检查连接池配置是否合理版本升级前测试ES异常场景下的行为建立binlog位置校验机制这次故障的根本原因是Adapter在异常处理流程中存在资源泄漏问题而默认配置下的连接池没有启用泄漏检测。通过这次深度排查我们不仅解决了眼前的问题更建立了预防类似问题的完整监控和运维体系。