3. Canal入门:工作原理、架构与安装配置
好,咱们今天聊聊Canal。说实话,在CDC工具里,Canal算是国内用得最广的开源方案之一了。我最早接触它是在2018年,那时候公司要做订单系统的实时同步,调研了一圈,最后选了Canal。为什么?因为它直接怼着MySQL的binlog干,延迟低,侵入性小。
这一节,我会把Canal的核心原理、架构设计,还有怎么装怎么配,一次性讲清楚。你跟着走一遍,基本就能上手了。
3.1 Canal的工作原理
Canal说白了,就是把自己伪装成一个MySQL的从库。你想想看,MySQL主从复制是怎么玩的?主库把变更写到binlog,从库拉过来回放。Canal干的也是同样的事——它假装自己是一个slave,向master请求binlog,然后解析成你能消费的数据格式。
核心流程:
- Canal连接到MySQL,发送dump请求
- MySQL主库把binlog推送给Canal
- Canal解析binlog事件(insert、update、delete)
- 将解析结果投递到下游(消息队列、数据库、文件等)
这里有个关键点——binlog的格式。我在项目中遇到过一个问题:同事把binlog格式设成了STATEMENT,结果Canal解析出来的数据全是SQL语句,根本拿不到变更前后的字段值。所以,一定要用ROW格式。这是硬性要求。
避坑指南:我曾经因为binlog格式没配对,排查了整整一个下午。MySQL的binlog_format必须设为ROW,否则Canal拿不到完整的数据变更内容。另外,binlog_row_image建议设为FULL,这样会记录所有字段的旧值和新值。
3.2 Canal的架构设计
Canal的架构其实不复杂,但分层很清晰。我习惯把它拆成两个维度来看:Server 和 Instance。
一个Canal Server可以跑多个Instance,每个Instance对应一个MySQL数据源。你想想看,如果你有10个数据库要同步,只需要部署一个Canal Server,配10个Instance就行。每个Instance内部又有三个核心组件:EventParser、EventSink、EventStore。
下面这张图,是我自己画的一个架构示意,你看完就明白了。
3.3 三个核心组件详解
EventParser
这是Canal的"眼睛"和"耳朵"。它负责连接到MySQL,模拟slave协议,拉取binlog。拉下来之后,它会把binlog事件解析成Canal内部的数据结构——Entry。
每个Entry包含什么?我列一下:
- header:日志位置、时间戳、事务ID
- entryType:事务开始、数据变更、事务结束
- storeValue:真正的变更数据,序列化后的RowChange对象
我记得有一次线上问题,Canal突然不消费了。查了半天,发现是EventParser在解析一个超大事务时卡住了。后来我调整了binlog的max_binlog_size,把单个事务拆小,问题就解决了。
EventSink
EventSink是"过滤器"和"路由器"。Parser解析出来的Entry,会先经过Sink。你可以在这里做:
- 过滤掉某些表(比如只同步order表,不同步log表)
- 转换数据格式
- 决定投递到哪个Store
默认的Sink实现是GroupEventSink,它会按事务分组,保证同一个事务的Entry一起被处理。这个设计很聪明——避免了部分提交的问题。
EventStore
EventStore是Canal的"内存缓冲区"。它用了一个环形队列(RingBuffer)来暂存解析好的事件。为什么用环形?因为内存复用,减少GC压力。
我建议你关注两个参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
| canal.instance.memory.buffer.size | 16384 | 环形缓冲区大小(条目数) |
| canal.instance.memory.buffer.memunit | 1024 | 每个条目占用的内存单位(字节) |
个人经验:如果业务量很大,建议把buffer.size调大到65536。但要注意,每个条目默认1KB,65536个条目就是64MB。如果你的JVM堆只有256MB,可能会OOM。我一般按"峰值TPS × 5秒"来估算缓冲区大小。
3.4 Canal的安装与配置
安装Canal其实不复杂。我习惯用Docker,但这里也讲一下传统部署方式,因为生产环境有时候不让用容器。
3.4.1 环境准备
- JDK 1.8+(Canal是Java写的)
- MySQL 5.7+(建议8.0,兼容性更好)
- 开启binlog,ROW格式
MySQL侧需要创建一个Canal专用的账号:
-- 创建用户
CREATE USER 'canal'@'%' IDENTIFIED BY 'canal123';
-- 授权复制权限
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
FLUSH PRIVILEGES;
嗯,这里要注意。REPLICATION CLIENT权限是必须的,Canal用它来检查master的状态和binlog位置。我见过有人只给了REPLICATION SLAVE,结果Canal启动时报错,查了半天才发现少了个权限。
3.4.2 下载与启动
# 下载Canal
wget https://github.com/alibaba/canal/releases/download/canal-1.1.6/canal.deployer-1.1.6.tar.gz
# 解压
tar -zxvf canal.deployer-1.1.6.tar.gz -C /opt/canal
# 进入目录
cd /opt/canal
# 修改配置文件
vi conf/canal.properties
vi conf/example/instance.properties
3.4.3 核心配置项
canal.properties(Server级别):
| 配置项 | 说明 |
|---|---|
| canal.port | Canal Server端口,默认11111 |
| canal.destinations | Instance列表,逗号分隔,如:example,test |
| canal.instance.global.mode | 运行模式,manager或spring |
instance.properties(Instance级别):
| 配置项 | 说明 |
|---|---|
| canal.instance.master.address | MySQL地址,如127.0.0.1:3306 |
| canal.instance.dbUsername | 数据库用户名 |
| canal.instance.dbPassword | 数据库密码 |
| canal.instance.filter.regex | 表过滤规则,如:mydb\\..* |
| canal.instance.connectionCharset | 字符集,默认UTF-8 |
注意:表过滤规则用的是Java正则表达式。如果你要匹配所有库的所有表,写 .*\\..* 就行。但如果你只想匹配某个库,比如test库,写 test\\..*。注意反斜杠要转义。
3.4.4 启动与验证
# 启动Canal
./bin/startup.sh
# 查看日志
tail -f logs/canal/canal.log
tail -f logs/example/example.log
# 如果看到类似下面的日志,说明启动成功
# ## start the canal server[172.17.0.2:11111]
# ## the canal server is running now ......
启动之后,你可以用Canal自带的客户端测试一下:
# 启动Canal自带的简单客户端
./bin/startup.sh com.alibaba.otter.canal.example.SimpleCanalClientTest
然后在MySQL里执行一条insert,看看客户端能不能收到。如果能收到,说明整个链路通了。
我的习惯:第一次部署Canal,我建议先在本机用Docker跑一个MySQL实例,再跑Canal。这样出了问题好排查。等流程走通了,再上生产环境。我曾经在生产环境直接部署,结果binlog格式没配对,Canal一直报错,压力很大。
3.5 小结
这一节我们讲了Canal的核心原理——伪装成MySQL从库拉binlog。架构上分Server和Instance两层,Instance内部有Parser、Sink、Store三个组件。安装配置其实不复杂,关键是把MySQL的binlog格式配好,权限给对。