Canal 是阿里巴巴开源的一个基于 MySQL 数据库的增量日志解析工具,主要用于实时监控 MySQL 数据库的变更(如插入、更新、删除等操作),并将这些变更同步到其他数据存储系统(如 Elasticsearch、HBase、Kafka 等)。Canal 的工作原理是通过模拟 MySQL 的 Slave 节点,从 MySQL 的二进制日志(binlog)中读取数据变更,并将其解析为易于处理的结构化数据。
Canal的核心组件一般有这三个
- Canal Server:负责连接 MySQL 数据库,解析 binlog,并将解析后的数据发送给客户端。
- Canal Client:负责接收 Canal Server 发送的数据,并进行进一步处理(如写入到其他存储系统)。
- Zookeeper:用于管理 Canal Server 的集群配置和状态。
Canal 的官网是:https://github.com/alibaba/canal/
安装Canal Server
环境准备
- MySQL,官网上写当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x
- 部署Canal Server的服务器/虚拟机。Canal是基于java的,理论上支持java的平台都能装,我用Linux的
- jdk:版本不要太高,高版本的 JVM 有些参数已被废弃或移除,但Canal还在用,就会启动不了,建议装 JDK 8
安装步骤
- 修改MySQL的配置,支持 Binlog 写入功能 ,配置 binlog-format 为 ROW 模式,my.cnf 或my.ini中配置如下
ini
[mysqld]
log-bin=mysql-bin # 开启 binlog
binlog-format=ROW # 选择 ROW 模式
server_id=1 # 配置 MySQL replaction 需要定义,不要和 canal 的 slaveId 重复
-
MySQL中新增一个账户用于canal
mysqlCREATE USER canal IDENTIFIED BY 'canal'; GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ; -- 使用上一行将授予用户 canal 所有数据库的完全控制权限, FLUSH PRIVILEGES; -- GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%'; -- 创建一个名为 canal 的用户,密码为 canal -- 授予用户 canal 以下权限 -- SELECT:允许查询所有数据库和表。 -- REPLICATION SLAVE:允许作为从库复制数据(Binlog 同步必需)。 -- REPLICATION CLIENT:允许查看主从复制状态(监控必需)
-
下载canal并解压,地址:https://github.com/alibaba/canal/releases,下载canal.deployer这个前缀的
-
解压后应该能看到这些文件夹
shell
drwxr-xr-x 2 root root 4096 Jan 25 15:25 bin/
drwxr-xr-x 5 root root 4096 Jan 25 15:04 conf/
drwxr-xr-x 2 root root 4096 Jan 25 15:04 lib/
drwxrwxrwx 4 root root 4096 Jan 25 15:25 logs/
drwxrwxrwx 2 root root 4096 Jan 16 16:32 plugin/
- 修改配置:
vi conf/example/instance.properties
,主要有这些部分
properties
## mysql serverId
canal.instance.mysql.slaveId = 1234
#position info,需要改成自己的数据库信息
canal.instance.master.address = xxx.x.x.x:3306
#username/password,即第二步中的账号密码
canal.instance.dbUsername = canal
canal.instance.dbPassword = canal
canal.instance.connectionCharset = UTF-8
- 启动:
sh bin/startup.sh
- 查看Server日志确认下
tail -n 20 logs/canal/canal.log
cmd
2025-01-25 15:25:33.661 [main] INFO com.alibaba.otter.canal.deployer.CanalLauncher - ## set default uncaught exception handler
2025-01-25 15:25:33.665 [main] INFO com.alibaba.otter.canal.deployer.CanalLauncher - ## load canal configurations
2025-01-25 15:25:33.674 [main] INFO com.alibaba.otter.canal.deployer.CanalStarter - ## start the canal server.
2025-01-25 15:25:33.708 [main] INFO com.alibaba.otter.canal.deployer.CanalController - ## start the canal server[172.30.12.207(172.30.12.207):11111]
2025-01-25 15:25:34.621 [main] INFO com.alibaba.otter.canal.deployer.CanalStarter - ## the canal server is running now
- 查看 instance 的日志:
tail -n 20 logs/example/example.log
- 关闭:
sh bin/stop.sh
: (有时候关闭不了,把/canal/bin/canal.pid这个进程文件删了就行)
安装canal-admin(非必须)
- canal-admin设计上是为canal提供整体配置管理、节点运维等面向运维的功能,提供相对友好的WebUI操作界面,方便更多用户快速和安全的操作
- canal server 功能确实强大,但是配置起来也是真的繁琐,使用canal-admin能一定程度上减轻配置的压力。原本的配置是在conf中新增instance,而canal-admin将配置转移到了mysql中,这样管理起来更方便了
环境准备
- MySQL,用于存储配置和节点等相关数据(出于方便在这里我使用了Canal Server中配置的数据库)
- canal版本,要求>=1.1.4 (需要依赖canal-server提供面向admin的动态运维管理接口)
安装步骤
- 下载canal-admin并解压,地址:https://github.com/alibaba/canal/releases,下载canal.admin这个前缀的
- 解压后应该能看到这些文件夹
shell
drwxr-xr-x 2 root root 4096 Feb 4 10:46 bin/
drwxr-xr-x 3 root root 4096 Feb 4 10:46 conf/
drwxr-xr-x 2 root root 4096 Feb 4 10:29 lib/
drwxrwxrwx 2 root root 4096 Feb 4 10:48 logs/
-
修改配置
vi conf/application.yml
,(主要改数据库配置,我这里和上面用了相同的数据库)server:
port: 8089
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8spring.datasource:
address: xxx.xx.xx.xx:3306
database: canal_manager
username: canal
password: canal
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://{spring.datasource.address}/{spring.datasource.database}?useUnicode=true&characterEncoding=UTF-8&useSSL=false
hikari:
maximum-pool-size: 30
minimum-idle: 1canal:
adminUser: admin
adminPasswd: admin -
初始化数据库:在我们使用的数据库中执行脚本:conf/canal_manager.sql(如果数据库在本机的话,就直接运行下面语句即可,但如果不在的话,就在目标数据库中运行脚本,初始化SQL脚本里会默认创建canal_manager的数据库,建议使用root等有超级权限的账号进行初始化)
shell
mysql -u root -p
source conf/canal_manager.sql
#执行后会发现库中会增加一个canal_manager的数据库,有canal_adapter_config、canal_cluster、canal_config、canal instance_config、canal_nodeserver、canal_user这些表
- 启动
sh bin/startup.sh
- 查看 admin 日志
vi logs/admin.log
,无error且有Adding welcome page
即可 - 可以通过http://xxx.xx.x.x:8089/访问,默认密码:admin/123456
- canal-server端配置调整 ,这里有两种做法
- 使用canal_local.properties的配置覆盖canal.properties
- 使用
sh bin/startup.sh local
重新启动下Server端
新增配置
canal-admin的核心模型主要有:
-
instance,对应canal-server里的instance,一个最小的订阅mysql的队列
-
server,对应canal-server,一个server里可以包含多个instance
-
集群,对应一组canal-server,组合在一起面向高可用
这里简单使用,不涉及集群功能,略过这个
-
将我们最已经启动的server导入进去,端口都用默认
-
新增instance,记得修改mysql的地址
第二步的时候,出了一个bug,始终无法启动instance且没有任何输出日志,推测是我启动canal server的时候加载错了配置,后面我重启了下canal server又正常了。一定要注意安装步骤中的第8步。
canal-python
canal-python
是 Canal 的 python 客户端,负责接收 Canal Server 发送的数据,并进行进一步处理(如写入到其他存储系统),这里只做简单的演示canal-python
与 Canal 是采用的Socket来进行通信的,传输协议是TCP,交互协议采用的是 Google Protocol Buffer 3.0。
环境准备
- python >= 3
- pip install canal-python
- pip install "protobuf<4.0"(高版本的protobuf会有不兼容问题)
演示代码
python
import time
from canal.client import Client
from canal.protocol import EntryProtocol_pb2
client = Client()
client.connect(host='xxx.xx.xx.xx', port=11111)
# 认证 Canal 连接,用户名和密码为空(默认无需认证)
client.check_valid(username=b'', password=b'')
# 订阅者 ID, 订阅的目标,订阅所有数据库和所有表的变更数据(正则匹配 数据库.表)
# filter=b'study\\.staff',订阅 study 数据库中 staff 表的变更数据
client.subscribe(client_id=b'12', destination=b'staff', filter=b'.*\\..*')
while True:
# 获取最多 100 条 变更数据
message = client.get(100)
# 获取变更日志
entries = message['entries']
for entry in entries:
entry_type = entry.entryType
# Canal 记录了事务的 开始 (TRANSACTIONBEGIN) 和结束 (TRANSACTIONEND),但这部分数据不是具体的行变更,因此跳过。
if entry_type in [EntryProtocol_pb2.EntryType.TRANSACTIONBEGIN, EntryProtocol_pb2.EntryType.TRANSACTIONEND]:
continue
# 解析 binlog 变更数据。
row_change = EntryProtocol_pb2.RowChange()
# 将二进制存储值解析为 row_change 对象。
row_change.MergeFromString(entry.storeValue)
event_type = row_change.eventType
# 获取表的元数据,分别为 数据库名 和 表名 和 事件类型。
header = entry.header
database = header.schemaName
table = header.tableName
event_type = header.eventType
# 处理不同的数据库变更事件,比如DELETE 事件只需要删除前的数据,INSERT 事件只需要插入后的数据,UPDATE 事件需要修改前后的数据。
for row in row_change.rowDatas:
format_data = dict()
if event_type == EntryProtocol_pb2.EventType.DELETE:
for column in row.beforeColumns:
format_data = {
column.name: column.value
}
elif event_type == EntryProtocol_pb2.EventType.INSERT:
for column in row.afterColumns:
format_data = {
column.name: column.value
}
else:
# 官网上这里代码写的有点问题,format_data['before'] = format_data['after'] = dict(),指向同一个字典对象了,我简单改了下
format_data['before'] = {}
format_data['after'] = {}
format_data = {
"before": {col.name: col.value for col in row.beforeColumns},
"after": {col.name: col.value for col in row.afterColumns}
}
data = dict(
db=database,
table=table,
event_type=event_type,
data=format_data,
)
print(data)
time.sleep(1)
# client.disconnect()
-
运行脚本,如果成功了的话应该有如下输出
connected to xxx.xx.xx.xx:11111
Auth succed
Subscribe succed -
我修改了某行数据的name,可以看见before和after的不同
json
{'db': 'study', 'table': 'staff', 'event_type': 2, 'data': {'before': {'id': '12', 'name': 'aa11', 'department': 'tech', 'time': '2025-02-04 15:51:25'}, 'after': {'id': '12', 'name': 'aa', 'department': 'tech', 'time': '2025-02-04 17:06:29'}}}
写在最后
canal的工作原理:
- canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
- MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
- canal 解析 binary log 对象(原始为 byte 流)
为什么使用canal:
- 代替使用轮询数据库方式来监控数据库变更,有效改善轮询耗费数据库资源。
- 实时监控数据库的变更,然后更新其他数据源、消息队列、备份数据......
优点:
- 实时性高
- 深度适配 MySQL 协议,支持主从复制、GTID、事务等特性
- 天然适配阿里云产品
- 数据一致性高,基于 binlog 的顺序性,保证数据变更事件的顺序投递
缺点:
- 不是所有版本的MySQL都兼容,其他的数据库都不支持
- 部署和配置还是比较繁琐的,网上的知识点也不算全,文档更新慢(可以自己去git上提issue)
- 运维复杂度很高,查问题也有点难度
- 资源消耗比较大,尤其是高并发场景,Canal Server 的 CPU 和内存占用可能较高
- 原生监控指标较少,异常告警机制不够完善(可以用外部工具,或者二次开发)