
“Canal连接MySQL失败”这七个字几乎是所有接触Canal的人第一次启动时都会撞上的墙。我做过不少MySQL增量同步的项目周期性地在技术社群里看到有人贴出同一段日志Canal实例启动后不断打印connect failed或者Access denied然后陷入迷茫。很多人的第一反应是改密码、重启Canal但这样试上几轮往往毫无进展因为问题根本不在密码而在于对Canal连接模式的误解。这篇文章把我实际踩过的、以及帮别人排查过的Canal连接MySQL失败问题做一个归类总结把最常见的那几类经典错误一次性讲清楚同时给出能直接抄的配置和排查路径。1. 五分钟理解Canal连MySQL的底层逻辑1.1 Canal不是普通的JDBC查询它在模拟从库要真正排查连接问题得先搞清楚Canal是怎么连MySQL的。它跟普通业务应用连数据库完全不是一回事。普通应用连MySQL本质是建立一个会话然后执行SQL权限上只需要增删改查但Canal不一样它的核心机制是把自己伪装成一台MySQL从库向主库发送dump请求让主库以为“有一个新从机要开始同步了”然后把binlog实时推送过来。Canal拿到这些binlog字节流之后再解析成增量数据变更事件。这就带来了两个关键要求。第一Canal的账号必须拥有主从复制相关权限而不只是读写业务表的权限如果没有REPLICATION SLAVE和REPLICATION CLIENT权限即使密码填对了、连接也建立了后续的dump阶段依然会失败日志表现和“连不上”很像。第二Canal连接成功后做了两件事先是走一遍普通JDBC连接读取库表结构信息用于解析binlog时还原字段含义再以从库身份建立binlog同步通道。如果第一次连接成功、第二次失败日志会表现为“连上了但同步不了”这种状态最容易让人误判以为是网络抽风。理解了这两点再看各类报错就有谱了。Canal连接MySQL失败本质上逃不出三个层面网络链路问题、认证权限问题、binlog配置问题。下面我按这个分类把经典错误逐个过一遍。1.2 失败归类三类经典错误的判断思路我把日常见到的Canal连不上MySQL问题归成三大类每一类都有典型的日志关键字和固定套路。第一类是纯网络链路问题。报错往往是java.net.ConnectException: Connection refused或者java.io.IOException: connect failed很多时候一眼看上去像Canal配置写错了其实是用Canal所在的机器根本访问不到MySQL的3306端口。这类问题排查最快telnet一下端口基本就能定性。第二类是认证与权限问题。典型日志是Access denied for user canalxx.xx.xx.xx (using password: YES)。注意using password: YES只代表客户端带了密码并不代表密码一定错误也可能是授权host不匹配。MySQL 8.0下还经常见到Public Key Retrieval is not allowed这是认证插件导致的经典问题。第三类是binlog相关问题。这类最隐蔽因为网络通了、账号密码也对了Canal实例的日志看起来“连上了”但一旦主库产生写操作解析线程立刻抛错常见的是parse failed、TableId not found或者是启动阶段直接报Could not find first log file name in binary log index file。这类错误根因大多是binlog没开、格式不是ROW、或者server-id冲突。后面我会展开每一类。先说结论90%的Canal连接MySQL失败都能在“端口通不通、账号有没有权限、binlog格式对不对”这三板斧里找到答案。2. 经典错误一connect failed连都连不上2.1 报错长什么样Canal连接MySQL失败时最经典的日志在logs/example/example.log里核心信息大致是这样的ERROR com.alibaba.otter.canal.common.AbstractCanalLifeCycle - start failed java.io.IOException: connect failed at com.alibaba.otter.canal.server.netty.handler.ClientAuthenticationHandler.channelRead(...) ... Caused by: java.net.ConnectException: Connection refused注意一个细节很多同学只看logs/canal/canal.log但那个文件记录的是Canal Server自身的运行状态真正的instance连接MySQL的详细报错在logs/example/example.log里。这里example是默认的instance名称如果你在canal.properties里配置了别的destination那么对应的日志目录就是logs/你的实例名/你的实例名.log。判断连接问题先翻开这个文件看比在canal.log里一头雾水地翻有效得多。如果日志里出现Connection refused基本可以断定是TCP层压根没有建立连接也就是Canal这台机器到MySQL这台机器的3306端口这条链路不通。别急着怀疑密码TCP端口都连不上密码对错根本轮不到MySQL来判断。2.2 网络链路排查清单我把这类问题分成四个子问题按顺序排查下来基本不会漏第一MySQL的IP和端口是否填对了。检查instance.properties里canal.instance.master.address它必须是IP:端口格式例如192.168.1.10:3306。这里有个我见过很多次的低级错误有人把mysql.hostname这种配置方式带过来了直接写192.168.1.10不写端口Canal解析地址失败后会莫名奇妙地报错而且错误信息不直观。第二Canal所在机器到MySQL的网络是否可达。直接在Canal所在机器上执行telnet 192.168.1.10 3306或者更现代化的探测方式nc -zv 192.168.1.10 3306如果端口不通检查是不是跨了不同网段、有没有路由缺失。云环境里还要检查安全组规则是否放行了Canal这台机器的IP访问3306。第三MySQL进程是否真的监听了3306。在MySQL所在机器上执行netstat -tlnp | grep 3306如果输出里只有127.0.0.1:3306这样的行说明MySQL被配置成只监听本机回环地址外部机器自然连不上。这就是Kubernetes或Docker部署时最常见的一个坑容器里的MySQL进程默认绑定了127.0.0.1端口映射做了也没用宿主机外面访问还是失败。解决办法在2.3里详细说。第四MySQL有没有开skip-networking。这个参数一旦开启MySQL干脆不监听TCP端口只接受本地Unix Socket连接外部程序无论如何都连不上。检查方法SHOW VARIABLES LIKE skip_networking;如果Value是ON注释掉配置里的skip-networking并重启MySQL。2.3 MySQL服务侧配置bind-address和防火墙MySQL的常规配置路径是/etc/my.cnf或/etc/mysql/my.cnf核心改动如下[mysqld] bind-address 0.0.0.0 port 3306bind-address默认值是127.0.0.1表示只接受本机连接。改成0.0.0.0就是监听所有网卡这样Canal所在的机器才能通过TCP访问。改完之后必须重启MySQL然后再次执行netstat -tlnp | grep 3306确认监听地址变成了0.0.0.0:3306。提示MySQL 8.0里还有mysqlx-bind-address那是X Plugin的监听地址主要服务MySQL Shell的X Protocol跟Canal走传统3306端口无关不要改错了。防火墙方面Linux常见的是firewalld和iptables云服务器还有安全组。检查命令分别是firewall-cmd --list-ports iptables -L -n | grep 3306如果MySQL跑在Docker里还要检查容器启动时的端口映射。比如docker run -p 3306:3306表示宿主机3306映射到容器3306如果漏了-p参数外部照样连不上。这个属于部署层面的低级错误但实际发生频率相当高。2.4 快速恢复步骤这类问题定位清楚后恢复动作其实很简单。按顺序做以下四步修改my.cnf把bind-address改为0.0.0.0确认没有skip-networking。重启MySQL执行netstat -tlnp | grep 3306确认监听地址正确。在Canal所在机器执行telnet IP 3306能通之后再进行下一步。重启Canal实例观察example.log里是否还有connect failed。我个人的经验是一旦telnet能通这类错误基本当场解决后面问题大概率转移到账号权限上也就是下一节要说的内容。3. 经典错误二Access denied账号密码和权限的坑3.1 报错内容与真实原因网络通了之后Canal日志里大概率会出现这一类报错java.io.IOException: connect failed ... Caused by: java.sql.SQLException: Access denied for user canal192.168.1.20 (using password: YES)注意这里的关键是报错里给出了三个信息用户名canal、来源IP192.168.1.20、以及using password: YES。很多人看到using password: YES就以为密码不对其实它只是告诉你“我带着密码登录了”并没有说密码一定是错的。真实原因有三个可能第一密码确实和MySQL里的记录不一致。这个没什么好说的直接在Canal配置里核对。第二用户名在MySQL里根本不存在。Access denied和用户不存在时MySQL为了安全故意返回同样的报错不告诉你到底是用户不存在还是密码错这是一种保护机制。第三授权host不匹配。这是最容易被忽略的坑。Canal从192.168.1.20这台机器发起连接但MySQL里授权的是canallocalhost或canal127.0.0.1那MySQL同样会返回Access denied哪怕密码完全正确。MySQL授权表是“用户名来源主机”双条件匹配来源主机不在白名单里一概拒绝。3.2 一次性创建Canal专用账号Canal官方文档建议用专用账号不要拿业务账号去跑同步。这是因为业务账号往往权限过小没有复制权限或者在权限回收时被误伤。我建议按下面的SQL创建SQL里的密码自己替换这里只是示例CREATE USER canal% IDENTIFIED WITH mysql_native_password BY canal; GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO canal%; FLUSH PRIVILEGES;注意几点。SELECT权限用来给Canal读取表结构信息没有它Canal解析binlog时拿不到字段信息会出现“同步了但解析出来的数据结构不完整”的诡异问题。REPLICATION SLAVE和REPLICATION CLIENT是Canal作为模拟从库接收binlog的必须权限。这两个复制权限是全局级的所以授权对象必须是*.*不能写成某个库名否则启动时就会报权限不足。FLUSH PRIVILEGES在直接使用CREATE USER和GRANT语句时其实不是必须的因为权限表已经被修改并加载了。但执行一下无妨尤其在使用了INSERT方式手工改过mysql.user表之后这句就是必须的。出于习惯我每次都执行顺手也省得后续因为权限表没刷新产生莫名其妙的问题。创建完之后一定要验证一下SHOW GRANTS FOR canal%;输出里应该包含GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO canal%3.3 MySQL 8.0认证插件问题MySQL 8.0把默认认证插件从mysql_native_password换成了caching_sha2_password这对安全是好事但对Canal这类中间件来说早期版本的兼容性是个大坑。我用过几个修复场景最典型的就是连接时报下面这个错Public Key Retrieval is not allowed原因是caching_sha2_password在建立SSL加密或RSA密钥交换时需要客户端先获取服务器的公钥而新版MySQL驱动出于安全考虑默认不允许直接检索公钥。Canal内置的驱动如果版本偏老或者没有启用AllowPublicKeyRetrieval就会卡死在这里。解决办法有两条路。第一条路也是最稳的路把Canal用户的认证插件改回mysql_native_password。在MySQL 8.0上重新创建用户的SQL就像3.2节那样显式加IDENTIFIED WITH mysql_native_password。老版本MySQL5.6、5.7本身默认就是mysql_native_password不需要额外处理。第二条路升级Canal版本。Canal 1.1.4及以后版本对MySQL 8.0支持度明显改善内置的JDBC驱动版本也新很多认证问题在新版本里已经不存在了。如果项目允许直接升级Canal版本比改认证插件要省心也减少了弱认证插件带来的安全风险。另外Canal的JDBC连接串里还有一个隐藏参数可以配置在instance.properties里的canal.instance.master.jdbc.url中可以增加allowPublicKeyRetrievaltrueuseSSLfalse。但我的建议是尽量用前两种方式绕开这个参数因为改了它之后Canal和MySQL之间的加密行为会发生变化后续不好排查链路问题。3.4 host范围、最小授权与实际项目建议说到host范围canal%里的%代表任意主机都能用这个账号连接。生产环境如果担心安全可以把%替换成Canal服务器所在的具体IP像这样CREATE USER canal192.168.1.% IDENTIFIED BY canal;这样只有192.168.1这个网段的机器能使用该账号。好处是即使密码泄漏外部机器也连不上。坏处是如果Canal和MySQL之间的网络有代理或跳板来源IP会显示为代理IP容易配错。所以我习惯先按实际来源IP授权验证跑通了再缩成网段。还有一个和host相关的坑如果MySQL开启了skip_name_resolve它就不做反向DNS解析此时授权host写域名几乎必然失败最好直接写IP。否则你会看到日志里出现Host 192.168.1.20 is not allowed to connect to this MySQL server授权表里明明写的是canal%也会拒绝因为MySQL只有在关闭skip_name_resolve时才允许用域名匹配且解析速度对每个连接都有影响。4. 经典错误三binlog相关从源头解析不了4.1 连上了但同步不了binlog未开启和格式错误的典型表现网络通、账号权限也对Canal日志里出现了类似connected to mysql这样的成功字样但业务表一产生变更同步就出现异常或者干脆在启动阶段报错。这一类问题核心在binlog。先看启动阶段的典型报错Caused by: java.io.IOException: Could not find first log file name in binary log index file这个报错很直白Canal去MySQL服务器上找binlog文件列表结果MySQL告诉它一个文件都没有。根因就是MySQL根本没开启binlog。Canal的运作完全依赖binlog这等于让一个靠“查账本”工作的人去面对一本不存在的账本自然没法干活。再看运行阶段的典型报错。如果binlog开了但格式不对Canal会报类似com.alibaba.otter.canal.parse.exception.CanalParseException: parse failed Caused by: java.io.IOException: EOF ...或者在某些场景下报TableId not found。这个TableId not found特别折磨人它通常出现在Canal按binlog里的table_id去映射表结构时发生了错位最典型的触发场景之一就是binlog里的写操作事件格式和Canal预期不一致。实际上Canal要求binlog必须以ROW格式记录因为只有ROW格式下binlog里才包含了每一行数据变更前后的完整内容。如果主库用的STATEMENT格式binlog只记录执行过的SQL语句Canal解析的时候拿不到“哪一行变了”解析自然失败。注意STATEMENT格式下Canal甚至能建立连接、能收到binlog事件直到解析阶段才爆雷这是它比“binlog没开”更隐蔽的原因。4.2 一份能跑通Canal的MySQL基础配置针对Canal的binlog要求MySQL端至少要满足三个条件开启binlog、格式为ROW、行的镜像记录完整。对应的my.cnf配置如下[mysqld] server-id 1 log-bin mysql-bin binlog_format ROW binlog_row_image FULL逐项解释一下。server-id是必须的MySQL主从复制体系要求每个参与者都有一个唯一IDCanal模拟从库也会占用一个IDserver-id都不存在的话MySQL无法开启binlog。这个值可以自己定只要和现有实例不冲突就行。log-bin mysql-bin是开启binlog并指定文件前缀。开启后MySQL会在数据目录下生成形如mysql-bin.000001的日志文件。注意修改这个参数后必须重启MySQL才能生效。binlog_format ROW这里不再重复解释直接记住没有这个参数Canal要么解析失败要么拿不到完整的行级变更。binlog_row_image FULL是进一步确保ROW模式下binlog记录完整的前后镜像。MySQL默认其实也是FULL但显式写出来能防止某些版本或云数据库默认值被改掉。配置改完重启MySQL然后在MySQL里执行下面这组SQL确认生效SHOW VARIABLES LIKE log_bin; SHOW VARIABLES LIKE binlog_format; SHOW VARIABLES LIKE binlog_row_image;期望值分别是log_bin为ONbinlog_format为ROWbinlog_row_image为FULL。再执行SHOW BINARY LOGS;应该能看到类似mysql-bin.000001的文件列表。看到这个列表Canal后续dump binlog的素材就有了。4.3 server-id冲突一个隐蔽的连接中断原因binlog配置没问题之后我会额外检查一个容易忽略的点server-id冲突。Canal启动后是作为“从库”连接主库的MySQL主库靠server-id来区分每个从库。如果Canal配置的server-id恰好跟现有某个从库一样主库会认为是同一台从机的重复连接直接把新的旧的一起踢下线。现象是Canal刚启动时正常能同步几分钟甚至几十分钟然后日志里出现连接被断开、重连又断开周而复始同时MySQL主库的错误日志里会看到关于slave I/O thread的相关提示。解决办法在instance.properties里给Canal指定一个和所有现有从库都不重复的ID配置项是canal.instance.mysql.slaveId 1234这个值建议设成一个不太容易被占用的数字比如IP后几位的组合。在MySQL里检查当前所有从库的IDSHOW SLAVE HOSTS;看清楚输出里已有的serverId让Canal的slaveId避开这些值问题就排除了。这类问题不常见但一旦碰到毫无头绪的排查过程非常痛苦所以单独拎出来说。4.4 改完配置后的完整验证链路当网络、账号、binlog都配置好后我一般按下面的验证链路走一遍确保真正跑通第一步重启MySQL如果改了my.cnf。第二步在MySQL里执行SHOW BINARY LOGS确认binlog文件存在执行SHOW VARIABLES LIKE binlog_format确认格式。第三步重启Canal并打开logs/example/example.log。如果一切正常应该能看到类似dump success或start dump mysql binlog这样的关键字没有ERROR级别日志。第四步在主库执行一条任意更新语句比如UPDATE一张测试表的一行数据回到Canal的消费端看是否及时拿到变更事件。最后这一步特别重要。光看Canal日志显示“连接成功”不代表整个链路就通了只有实际产生一次写操作、Canal端真的解析出事件才算真正闭环。我自己在验证阶段从来不会跳过这个动作宁可多花两分钟也不让隐藏问题带到生产环境。5. 常见问题速查表与排查路径5.1 典型报错速查表结合前面的内容我把Canal连接MySQL失败里最经典的几个报错整理成一张速查表方便直接对照日志关键字根因处理方式java.net.ConnectException: Connection refusedTCP端口不通、MySQL未监听或bind-address限制telnet探测、检查3306监听地址、防火墙放行Access denied for user canal... (using password: YES)密码错误、用户不存在、授权host不匹配核对密码、重建账号、用IP网段重设hostPublic Key Retrieval is not allowedMySQL 8.0认证插件不兼容改mysql_native_password或升级CanalHost ... is not allowed to connect to this MySQL server授权host不匹配或启用了skip_name_resolve改授权host为实际来源IPCould not find first log file name in binary log index filebinlog未开启或无binlog文件配置log-bin并重启MySQLCanalParseException: parse failed / EOFbinlog格式非ROW或binlog文件异常改binlog_formatROWTableId not foundbinlog与表结构映射错位常见于格式不对确认ROW格式检查表结构是否被改动同步一段时间后连接断开并重连server-id冲突修改canal.instance.mysql.slaveId避开现有从库这张表我贴过很多次每次帮别人排查时发现问题基本都能落到其中一两行。建议大家先把报错日志完整复制出来拿关键字去匹配不要凭感觉瞎猜。5.2 排查思路总结按链路推进如果不想依赖速查表就用链路推进法。Canal连接MySQL失败本质上是从“网络”到“认证”再到“binlog”逐层递进的问题。我在排查时永远遵循一个原则先看Canal实例日志再做端口探测再查账号权限最后查binlog配置。顺序不能乱否则很容易折腾半天发现一开始的问题方向就是错的。具体流程可以这样走打开logs/example/example.log找到第一条ERROR日志记录关键字。如果报错是Connection refused直接telnet端口处理网络层。如果报错是Access denied先执行SHOW GRANTS确认账号权限和授权host。如果报错是Could not find first log file name或parse failed检查log_bin、binlog_format、binlog_row_image这三个变量。如果以上都正常但同步一段时间断开检查MySQL的SHOW SLAVE HOSTS排查server-id冲突。最后一定做一次真实写操作验证确认Canal端成功解析出数据变更。每一步都有明确的验证动作不会陷入“改了配置但不知道有效没效”的盲区。5.3 一些经验与建议最后分享几个从多次踩坑里沉淀下来的习惯算不上什么高深技术但很实用。第一优先看实例日志而不是Server日志。Canal有两个日志目录logs/canal/canal.log和logs/example/example.log。很多人一上来就打开canal.log看到一大堆框架日志找不到重点。实际上instance日志里才是Canal跟MySQL实际通信的记录排查连接问题先看它。第二改配置后不要只重启Canal。如果改的是MySQL侧配置bind-address、binlog参数一定要重启MySQL。我就见过有人改了my.cnf之后只重启CanalMySQL根本没有加载新配置然后Canal这边来回试了半小时直到我提醒才反应过来。第三Canal版本和MySQL版本的匹配关系要提前确认。MySQL 8.0普及之后我接触过不少“连不上”的问题最后查出来都是Canal版本太老完全兼容不了新认证插件。遇到这类情况升级Canal版本往往是最干净的解法比在MySQL侧做各种妥协配置要省事。第四所有配置改动一点点来一次只改一个变量。网络不通就只改网络认证不过就只改账号不要同时改bind-address、密码、binlog_format三样东西然后在结果不明时生命现象。逐项变更、逐项验证定位问题的速度反而更快。这些经验看着朴素但真到排查的时候能帮你省下好几个小时的无效操作。写在最后Canal连接MySQL失败的坑说白了就是“网络、账号、binlog”三座山翻过这三座山Canal的同步链路基本就通畅了。我个人在实际操作中的体会是这种问题最怕的不是报错本身而是报错信息被曲解。Access denied不一定是密码错connect failed也不一定是网络故障先理解Canal的模拟从库机制再按链路逐层排查大多数经典错误都能在十分钟内定位。最后再分享一个小技巧排查这类问题前先把Canal的instance.properties和MySQL的my.cnf里跟连接相关的配置都加一行注释标明改动的日期和原因。我有一次排查一个线上连接间歇性断开问题最后发现是两周前有人为了测试把binlog_row_image改成了MINIMAL没改回来。有配置留痕的话这类问题一眼就能看到根本不用查半天。