资讯动态

Canal HA高可用部署实战:基于ZooKeeper的MySQL binlog同步故障切换方案

发布时间:2026/10/24 6:10:32 来源:尧图企业网站定制
先说一个让我印象深刻的凌晨。当时项目里跑着单机版canal负责把MySQL的binlog同步到另一套存储里做缓存刷新。凌晨两点多数据库做了主从切换canal直连的旧主库连接立刻断掉进程还在但日志里全是重连报错。第二天早上同事发现缓存落后了将近四个小时更麻烦的是旧主库的binlog已经被清理了一部分意味着丢失的数据只能靠重新全量补齐。那次之后我下定决心把canal的HA方案落地。这篇文章就围绕canal在Linux环境下的HA安装配置来写。我会把部署前的架构认知、环境准备、两份配文件的每个关键参数、故障切换验证、常见排错、运维经验全部讲透。适合正在单机跑canal不敢动的团队也适合第一次接触canal想一步到位做高可用的人。文章里的操作步骤基于canal 1.1.x版本和ZooKeeper 3.6.x以上版本都可以直接照着做。1. 为什么单机canal总是让人半夜惊醒1.1 三个常见的故障场景我见过太多团队把canal当成部署完就不用管的组件其实它和任何一个在线服务一样都会遇到进程挂、网络断、配置错这类问题。单机部署时最常见的故障是以下三种。第一种是进程被系统杀掉。Java进程的内存长时间占用过高被操作系统OOM Kill。这种情况最诡异——你去看监控canal进程不明不白消失了没有任何日志因为OOM Kill不一定每次都留下堆栈。第二种是底层MySQL发生切换canal里配的master.address失效它只会反复重连不会自动跟着切到新主库。第三种是canal所在机器磁盘写满、网卡故障、机房断电整个进程就再也起不来了。这些场景我全部遇到过。每次故障的影响看起来不大——无非是缓存没更新、同步延迟——但在数据链路上延迟意味着用户看到的数据是旧的数据不一致带来的问题有时候比宕机更严重。1.2 canal HA的边界高可用不等于横向扩展很多人第一次听到canal HA第一反应是搞两台机器分担压力。这个理解要纠正一下。canal的高可用不是负载均衡它是典型的主备模式。同一个destination下的多台canal server同一时刻只会有一个实例真正连上MySQL拉取binlog并解析推送这个实例叫master。其他实例处于standby状态它们不建立MySQL连接也不消费binlog只是通过ZooKeeper盯着master的心跳。一旦master进程挂掉或者网络分区ZooKeeper上的临时节点消失一个standby实例会被自动提升为master。用个生活化的比喻一间教室安排两个值日生但日常只有一个值日生在干活另一个在旁边待命。干活的值日生突然请假旁边待命的马上接手继续扫地。可是两个人从来不会同时拿起扫帚因为扫帚只有一把。所以如果要提升canal的解析吞吐量靠HA是解决不了的应该考虑多destination拆分、并行通道这些扩展方式。HA只保证一件事单点故障不影响链路可用性。这一点在部署前就要向团队讲清楚免得大家产生错误预期。2. 环境准备HA方案里最容易被忽视的三个基础件2.1 ZooKeeper整个协调机制的心脏canal的HA完全依赖ZooKeeper来做主节点协调、位点存储、故障感知。可以说ZooKeeper集群的稳定性直接决定canal HA靠不靠谱。生产环境我建议用三个节点组成的ZooKeeper集群即使canal只有两台机器。两个节点也可以但ZooKeeper本身出现单点故障时canal的HA能力就会退化为0等于白搭。三个节点里挂掉一个集群依然能正常选出新的leadercanal不受影响。安装ZooKeeper不算复杂解压官方tar包后修改conf/zoo.cfg# zoo.cfg tickTime2000 initLimit10 syncLimit5 dataDir/data/zk clientPort2181 # server.1等配置对应myid server.1192.168.10.11:2888:3888 server.2192.168.10.12:2888:3888 server.3192.168.10.13:2888:3888需要特别提醒的是dataDir目录的权限ZooKeeper一般用专用账号运行dataDir没有写权限的话进程会直接启动失败。同时三个节点的myid文件内容必须和server.X里面的编号对应这个低级错误我见过不止一次。启动后一定要用bin/zkServer.sh status确认角色看到leader/follower各就各位再往下走。如果三个节点全是standalone状态说明集群没连起来大概率是防火墙没放行2888/3888端口。2.2 MySQL侧binlog与账号权限canal本质上是一个伪装成MySQL从库的客户端它通过replication协议拉取binlog。所以MySQL侧的配置直接决定canal能不能正常工作。HA方案不用说对MySQL侧的要求只会更严格。binlog必须开启格式必须为ROWbinlog_row_image建议设为FULL这样才能保证更新操作前后完整的数据都能拿到。低版本MySQL可能默认是minimal解析出来的数据不完整后面做数据消费时会很难受。然后是账号权限。canal需要的最小权限是SELECT、REPLICATION SLAVE、REPLICATION CLIENT。这三个权限分别用于查询表结构、请求binlog、查询master状态。创建账号参考下面这段SQLCREATE USER canal% IDENTIFIED BY your_password; GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO canal%; FLUSH PRIVILEGES;权限给大了不安全给小了启动时报错。比如只给REPLICATION SLAVE不给SELECTcanal解析表结构时会拿不到元数据导致解析出来的字段对不上。生产环境做权限最小化没毛病但别把SELECT漏了。2.3 JDK、安装包与目录规划canal本身是纯Java应用JDK 8及以上都能跑。Linux下我习惯用OpenJDK 1.8稳定省心。注意设置JAVA_HOME另外尽量不要用root用户跑canal可以单独建一个应用账号避免权限问题影响其他服务。canal安装包叫canal.deployer-1.1.x.tar.gz解压后的目录结构是bin、conf、lib、logs、plugin。其中conf目录下包含全局配置canal.properties以及每个实例一个子目录实例子目录里是instance.properties。我建议在两台机器上使用完全相同的目录结构比如都放在/opt/canal/canal.deployer这样脚本和配置发方便管理排查问题时也不容易搞混。3. 部署配置把每个参数后面的原理说清楚3.1 两套配置文件的职责分工canal的配置容易把人绕晕其实只要分清两套配置文件的职责就好。conf/canal.properties是全局配置管的是canal server进程本身包括ZooKeeper地址、集群模式、端口、加载哪些instance。conf/{destination}/instance.properties是单个实例的配置管的是这个instance怎么连接MySQL、从哪个位点解析、过滤哪些表。HA模式下两台机器上的canal.properties基本一样只是canal.registerIp要各自填自己的IP。而两台机器上的instance.properties也基本一样唯一必须不一样的是canal.instance.mysql.slaveId。你想想canal实例连接MySQL时会被MySQL当作一个slave来登记两个实例如果slaveId相同主库的复制调度会直接打架甚至报错。3.2 canal.properties关键项逐条拆解先看一份实际可用的canal.properties片段主要关注和HA相关的部分# 集群模式下必须配置ZooKeeper地址逗号分隔 canal.zkServers 192.168.10.11:2181,192.168.10.12:2181,192.168.10.13:2181 # 集群模式开关 canal.instance.global.mode zookeeper # 使用支持ZooKeeper协调的spring配置 canal.instance.global.spring.xml classpath:spring/default-instance.xml # 当前进程加载的destination列表 canal.destinations product_order # 当前进程对外提供服务的端口 canal.port 11110 # 多网卡环境下需要手动指定当前节点注册到ZooKeeper的IP canal.registerIp 192.168.10.21一个个说清楚。canal.zkServers是HA的地基。要注意它填的是客户端连接地址clientPort2181不是三个节点的集群间通信地址。有些同学把2888端口的地址填进来导致canal连不上ZooKeeper。canal.instance.global.mode zookeeper用于开启集群模式。这边的原理是canal会有两种运行模式默认的spring模式是把配置放在本地每个实例各自处理改成zookeeper模式后实例在启动时会把自己的状态注册到ZooKeeper并通过ZooKeeper协调谁当master、谁做standby。canal.instance.global.spring.xml这里是一个关键的取舍。canal提供了几种spring配置文件模板HA场景下必须使用default-instance.xml这个模板会从ZooKeeper读取位点信息。如果误用了meta-instance.xml之类的本地存储模板HA部署下主备切换后位点对不上数据会出现重复或丢失。canal.destinations声明的destination就是实例名。两台机器的这个参数要保持一致比如都配置product_order这样ZooKeeper在协调instance时才知道哪两台机器是在为同一个destination服务。3.3 instance.properties关键项逐条拆解再看conf/product_order/instance.properties# 实例在MySQL侧呈现的slaveId必须唯一不能和现有从库重复 canal.instance.mysql.slaveId 20241 # 主库地址如果MySQL本身也有主备可以再配置standby.address canal.instance.master.address 192.168.10.100:3306 canal.instance.dbUsername canal canal.instance.dbPassword your_password canal.instance.connectionCharset UTF-8 # 解析过滤规则默认解析所有库表 canal.instance.filter.regex .*\\..*这里放一个非常容易踩的坑canal.instance.master.address和standby.address指的是MySQL的主备地址不是canal实例的地址。这个字段的意义是让canal在MySQL主备切换时能够跟着切换底层连接。而canal多个实例之间的主备切换是靠ZooKeeper完成的和这两个字段没有直接关系。我的实际经验是除非你们确实在数据库层也做了主备切换否则可以不配standby.address。因为对于HA组里的两台canal来说它们连接的是同一个MySQL地址真正的故障切换是canal实例层面由ZooKeeper完成的。硬要配一个standby地址反而可能让切换行为变得复杂。canal.instance.mysql.slaveId我前面说过两台机器必须不同。另外也不能和现有MySQL从库的slaveId重复。我第一次配置HA时没注意这个结果第二台canal启动后一直报Slave failed to initialize relay log之类的错误MySQL主库日志里也出现replication冲突。3.4 为什么一机一实例是硬性要求官方文档对canal HA有一个隐含约束每个canal server在分布式情况下不要部署多个instance。也就是说HA模式下一台机器上只跑一个destination实例。原因和客户端获取连接有关。客户端通过ZooKeeper获取主canal实例地址时如果一台机器上同时跑着多个destinationZooKeeper返回地址后客户端会默认这台机器上的某个destination是可以连接的。但实例之间并不知道彼此的存在这种混乱会导致连接串线客户端拉到的数据根本不是自己订阅的那个库。我在一开始也尝试过一台机器上部署两个实例用不同端口区分结果在故障切换测试时有一半的请求落到了错误的instance上。排查了很久才发现是违背了一机一实例的约定。后来规规矩矩一台机器一个实例问题立刻消失。4. 故障切换验证纸上谈兵不算数4.1 正常启动时的角色确认配置完成后启动顺序是先确认ZooKeeper集群健康再启动canal节点。启动canal用bin/startup.sh它本质是把java进程拉起来。启动之后不要立刻下结论去日志里确认角色。日志位置是logs/{destination}/{destination}.log比如logs/product_order/product_order.log。以我的经验master节点日志中会出现类似下面这些关键词success to register canal server: ... with address: /192.168.10.21:11110 FreshCursor optimize update Start mysql connection to ... dump binlog ...而standby节点的日志中一般会出现... has become standby看到standby日志里的has become standby说明第一台节点很清楚地知道自己的角色是待命。如果两台日志里都出现master相关的内容说明ZooKeeper协调没有生效多半是配置了canal.zkServers但mode还是spring。另外logs/canal/canal.log是server层面的日志配合查看能获得更完整的上下文。4.2 故障注入实验kill掉master配置完并不是终点我当时做了一次完整的故障注入实验验证这套HA不是纸面能力。方法是直接找到master进程用kill -9杀掉它。然后立刻盯住standby机器的实例日志。正常情况下几秒内standby日志会出现新的master角色提升日志随后开始建立MySQL连接打印dump binlog的接收进度最后日志里位点信息和master挂掉前的位点衔接上。整个过程我用秒表掐过从kill到standby开始拉取binlog大概在3到5秒之间。这个时间包含ZooKeeper临时节点退场、事件通知、备机竞争锁重新注册三部分。实验做完还有一件事要检查把master进程重启。重启后的节点会自动发现当前已经有另一个master于是乖乖进入standby状态不会出现脑裂、双写的情况。这一点可以放心因为canal在ZooKeeper上对主节点有严格的临时节点排他策略后续启动的实例发现cluster节点下已经有主了就会自动退让。4.3 验证数据连续性重复和丢失都要看故障切换能发生不等于数据不丢不重。binlog的消费语义是at-least-once也就是说极端场景下客户端可能收到重复数据。我建议在切换实验时同时运行一个简单的消费程序往一张测试表里不断写入数据消费端打印收到的每一条binlog变更。切换结束后对比消费端收到的变更记录和实际写入记录确认两件事第一没有大段丢失第二如果有重复消费端是否有幂等机制兜底。canal客户端本身的连接方式也要说清楚如果你还在用newSingleConnector直连master的IP端口切换后客户端不会自动连到新的master业务就断了。正确做法是使用ZooKeeper感知的集群连接方式。Java客户端的示例代码String zkServers 192.168.10.11:2181,192.168.10.12:2181,192.168.10.13:2181; CanalConnector connector CanalConnectors.newClusterConnector(zkServers, product_order, , );用newClusterConnector连接ZooKeeper地址客户端会先从ZooKeeper查询当前master的地址再建立真正的连接。master切换后客户端代码里需要建立重连循环比如捕获CanalClientException后重新调用connect()和subscribe()。这样才能实现消费端无人工干预的故障恢复。5. 排错HA搭建最常见的五个问题配置好之后如果没有一步到位大概率遇到下面这些情况。我用表格整理一下典型症状和对应的排查方向方便你按图索骥。症状可能原因排查方法启动时报无法连接ZooKeeperzk地址错误、防火墙未放行2181用telnet测试三个zk节点2181检查zk集群状态两台日志都显示mastermode未配成zookeeper或者spring.xml用错检查canal.instance.global.mode和spring配置备机一直不接管备机内存不足、配置文件中destination不一致查看备机日志的ERROR堆栈确认canal.destinations一致连接MySQL时报权限不足账号缺少REPLICATION SLAVE或SELECT重新执行3.2节的授权SQL并确认binlog_row_image切换后客户端拉不到数据客户端仍是直连旧IP模式改用newClusterConnector建立重连循环5.1 角色不切换的完整排查链路有一次我帮同事排查现象是手动kill掉master后备机等了很久都不接管日志里只有一堆无意义的刷屏。我把排查链路完整走了一遍在这里还原出来供参考。第一步确认ZooKeeper上还存在哪些临时节点。用命令行查看./bin/zkCli.sh -server 127.0.0.1:2181 ls /otter/canal/destinations/product_order/cluster正常情况下cluster节点下应有当前master对应的注册信息。如果master被杀后这个节点下的临时子节点仍然存在说明master进程虽然退出但ZooKeeper会话还没有关闭很可能是进程没有真正被杀或者有守护脚本把它拉起来了。这是我那次遇到的情况——机器上有个crontab任务发现canal进程消失就自动重启结果备机永远等不到接管的机会。第二步检查备机日志中是否有ZooKeeper watch事件。canal用watch机制监听cluster节点的变化如果日志中连watch事件都没有说明备机的canal.zkServers很可能配错了它根本没有监听同一个父路径下的节点变化。第三步确认两台机器回连MySQL账号的权限。备机升级为master的那一刹那是权限验证最容易暴露问题的时候。因为备机平时不建立MySQL连接权限有问题时在standby阶段看不出来只有在接管时才会报错。5.2 从权限到内存几个隐蔽的坑权限问题我遇到过另一个变种。canal账号权限一开始给的是.*库某个库做过账号权限收紧后canal立刻报Access denied但主库的其他链路正常。这种情况会误导人你以为网络不通或者密码错了其实是被授权策略调整误伤。排查时重点看MySQL端SHOW GRANTS。内存问题更隐蔽。默认canal启动脚本里给JVM堆内存设置比较大我记得在startup.sh中默认-Xms和-Xmx可能达到2G以上。如果服务器内存本身只有4G同时上面还跑着MySQL、ZK很容易触发OOM。特别是备机在平时看起来不占资源但一旦切换为masterJVM内存会迅速增长。所以建议修改bin/startup.sh中的JVM参数根据机器实际内存调到合理值我给一个参考#!/bin/bash JAVA_OPTS$JAVA_OPTS -Xms1g -Xmx1g -Xmn384m这里想表达的是内存配置要以任何一台机器随时可能成为master为前提来做不能用standby阶段的低水位去评估容量。6. 运维经验HA跑起来只是开始6.1 监控与告警HA部署完成后监控比部署更重要。我的经验是监控分三层进程层、日志层、数据层。进程层用简单脚本就行检查canal的进程是否存活、端口是否监听。ZooKeeper的临时节点存在性也可以作为监控指标用上面提到过的ls /otter/canal/destinations/...命令配合crontab轮询一旦发现cluster节点下没有master信息立即告警。日志层监控logs/{destination}/{destination}.log中的ERROR关键字。但注意不要只盯ERROR有些致命错误因为线程问题不会打ERROR而是WARN或者直接在canal.log里。我习惯把canal.log和instance.log都纳入日志采集。数据层监控最关键也最容易忽视消费端拿到的最后一条binlog时间和当前数据库时间之差。这个延迟指标比进程存活性更真实因为即使canal进程活着解析线程卡死、消费端堆积数据链路一样是坏的。用JMX或者通过canal的metrics端口拉取延迟再结合消费端水位做双校验。6.2 升级与维护节奏canal做版本升级时HA反而给了你一个非常舒服的滚动窗口。我的操作步骤是先把standby机器停掉升级代码启动并确认它回到standby角色再停掉master机器升级代码启动。由于ZooKeeper协调机制的存在master停掉后standby会自动接管业务无感。但有一个必须注意的点升级前备份完整的conf目录尤其是canal.properties和instance.properties。canal的配置项在不同版本之间会有细微调整比如1.1.5到1.1.7就调整过admin相关的默认参数直接覆盖可能把你原有的配置冲掉。还有个小技巧升级后不要立刻放心走人先观察一段时间的延迟指标。canal位点恢复在切换时可能有一些重复消费消费端如果幂等做得好两边数据是一致的但延迟指标能直接反映是否有回退。6.3 我个人运维中的体会canal HA这套方案我跑了将近两年最大的感受是它把canal进程挂了没人知道变成了一件不需要有人知道的事——前提是配置正确、客户端配合。最怕的就是那些在单机模式下积累的坏习惯比如客户端直连canal IP、进程守护脚本强制拉起、监控只盯进程不盯延迟这些习惯带到HA环境里会把高可用的价值削减掉大半。如果你现在正被canal的单点问题困扰建议按我的步骤先小范围实验两台测试机加一个ZooKeeper节点配置好后做一次故障注入亲自看着备机接管。这个过程跑通一次后面上生产就有底气了。最后再提醒一句HA方案验证的核心永远是故障注入实验只有实际kill过master你才敢说自己的同步链路是高可用的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑