MySQL 双主热备 + Canal Admin + Kafka 集成方案
概述
本方案介绍如何使用 Canal Admin 管理 Canal Server 集群,将 MySQL 双主热备集群的数据变更实时同步到 Kafka 集群,实现高可用的 CDC(Change Data Capture,变更数据捕获)功能。
前置条件
在开始本方案之前,请确保已经完成以下部署:
-
MySQL 双主热备集群
- 参考:MySQL 高可用故障转移(双主热备)
- 已开启 binlog,格式为 ROW
- 已创建 Canal 用户并授权
-
Kafka 集群
- 参考:Kafka 虚拟机部署指南
- 已创建用于同步数据的 Topic
-
Zookeeper 集群(如果使用传统模式)
方案优势
- 高可用性:MySQL 双主 + Canal 双实例,任一节点故障不影响数据同步
- 实时性:基于 MySQL binlog,实现毫秒级数据同步
- 低侵入:无需修改业务代码,通过 binlog 采集数据
- 易扩展:Kafka 天然支持多消费者,便于下游扩展
- 解耦合:生产者和消费者完全解耦,提升系统灵活性
- 可视化管理:通过 Canal Admin Web 界面管理 Canal Server 集群
- 动态配置:无需重启 Canal Server,即可 添加、修改、删除 Instance
- 集中管理:统一管理多个 Canal Server 节点
目录
1. 架构设计
1.1 整体架构
1.2 数据流向
MySQL 双主集群 → binlog → Canal Server 集群 → Canal Admin → Kafka 集群 → 消费者
详细流程:
- 应用通过 VIP (192.168.192.170) 访问 MySQL 主库
- MySQL 产生数据变更(INSERT/UPDATE/DELETE)
- MySQL 将变更记录到 binlog
- 双主之间自动同步 binlog
- Canal Server 模拟 MySQL Slave,拉取本地 MySQL 的 binlog
- Canal Server 解析 binlog,转换为结构化事件
- Canal Admin 通过 TCP 协议管理 Canal Server 集群
- Canal Admin 配置 Instance 的同步规则和目标
- Canal Server 将事件发送到 Kafka 集群
- Kafka 集群存储消息并多副本复制
- 消费者从 Kafka 拉取消息并处理
1.3 高可用机制
| 组件 | 高可用方案 | 故障切换时间 |
|---|---|---|
| MySQL | 双主复制 + Keepalived VIP | < 3 秒 |
| Canal Server | 双实例部署,各自连接本地 MySQL | < 5 秒 |
| Canal Admin | 单 实例部署,故障时可通过 Web 界面快速切换 | < 10 秒 |
| Kafka | 3 节点集群,KRaft 模式 | < 10 秒 |
2. 部署规划
2.1 服务器规划
| 组件 | 主机名 | IP 地址 | 角色 | 说明 | |------|--------|---------|------| | MySQL 主库 1 | ubuntu-mysql-node1 | 192.168.192.171 | MySQL + Keepalived 主 | MySQL 主库 1 | | MySQL 主库 2 | ubuntu-mysql-node2 | 192.168.192.172 | MySQL + Keepalived 备 | MySQL 主库 2 | | MySQL VIP | - | 192.168.192.170 | 虚拟IP | 应用访问入口 | | Canal Admin | ubuntu-canal-admin | 192.168.192.180 | Canal Admin Web 管理界面 | 管理 Canal Server 集群 | | Canal Server 1 | ubuntu-canal-node1 | 192.168.192.181 | Canal Server | 连接 MySQL 主库 1 | | Canal Server 2 | ubuntu-canal-node2 | 192.168.192.182 | Canal Server | 连接 MySQL 主库 2 | | Kafka 节点 | ubuntu-kafka-1 | 192.168.192.211 | Kafka Broker | Ubuntu 单节点部署 |
2.2 端口规划
| 组件 | 端口 | 协议 | 说明 |
|---|---|---|---|
| MySQL | 3306 | TCP | MySQL 服务端口 |
| Canal Server | 11111 | TCP | Canal Server 端口 |
| Canal Admin | 8089 | TCP | Canal Admin 端口 |
| Canal Admin Manager | 11110 | TCP | Canal Admin 管理端口 |
| Canal Metrics | 11112 | TCP | Canal 指标端口 |
| Kafka | 9092 | TCP | Kafka PLAINTEXT 端口 |
2.3 MySQL 前置配置
确保 MySQL 双主集群已满足以下配置:
# binlog 配置(两台 MySQL 都需要)
log-bin = /var/lib/mysql/mysql-bin.log
binlog_format = ROW
binlog_row_image = FULL
gtid-mode=on
log-slave-updates=true
enforce-gtid-consistency=true
2.4 创建 Canal 用户
在两台 MySQL 服务器上执行:
-- 登录 MySQL
mysql -hlocalhost -uroot -p
-- 创建 Canal 用户
CREATE USER 'canal'@'%' IDENTIFIED WITH mysql_native_password BY 'canal@123456';
-- 授予权限
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
-- 刷新权限
FLUSH PRIVILEGES;
-- 验证权限
SHOW GRANTS FOR 'canal'@'%';
quit;
2.5 创建测试数据库和表
在主库 1 上执行:
-- 登录 MySQL(通过 VIP)
mysql -h192.168.192.170 -uroot -p
-- 创建测试数据库
CREATE DATABASE IF NOT EXISTS test_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE test_db;
-- 创建测试表
CREATE TABLE IF NOT EXISTS user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
age INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 插入测试数据
INSERT INTO user (username, email, age) VALUES
('user1', 'user1@example.com', 25),
('user2', 'user2@example.com', 30),
('user3', 'user3@example.com', 28);
-- 查看数据
SELECT * FROM user;
quit;
3. Canal Admin 部署
3.1 Canal Admin 介绍
Canal Admin 是 Canal 的 Web 管理界面,提供了可视化的 Canal Server 集群管理功能。使用 Canal Admin 可以:
- 可视化管理:通过 Web 界面管理 Canal Server 集群
- 动态配置:无需重启 Canal Server,即可添加、修改、删除 Instance
- 监控指标:实时查看 Canal Server 的运行状态 and 性能指标
- 配置版本管理:支持配置版本回滚和对比
- 集群管理:统一管理多个 Canal Server 节点
3.2 环境准备
在 Canal Admin 服务器(192.168.192.180)上执行:
3.2.1 安装 Java
# 更新软件包列表
sudo apt update
# 安装 OpenJDK 11
sudo apt install -y openjdk-11-jdk
# 验证安装
java -version
# 设置 JAVA_HOME 环境变量
echo 'export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64' | sudo tee -a /etc/environment
source /etc/environment
3.2.2 创建 Canal Admin 用户
# 创建 canal 用户
sudo useradd -m -s /bin/bash canal
# 创建 Canal Admin 安装目录
sudo mkdir -p /opt/canal-admin
sudo chown -R canal:canal /opt/canal-admin
3.3 Canal Admin 安装
在 Canal Admin 服务器(192.168.192.180)上执行:
# 进入安装目录
cd /opt
# 下载 Canal Admin (使用 sudo 确保有权限)
sudo wget https://github.com/alibaba/canal/releases/download/canal-1.1.8/canal.admin-1.1.8.tar.gz
# 解压到指定目录
sudo tar -zxvf canal.admin-1.1.8.tar.gz -C /opt/canal-admin
# 统一赋值权限
sudo chown -R canal:canal /opt/canal-admin
3.4 创建 Canal Admin 数据库
在任意一台 MySQL 服务器上执行:
-- 登录 MySQL
mysql -hlocalhost -uroot -p
-- 创建 Canal Admin 数据库
CREATE DATABASE IF NOT EXISTS canal_manager DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
-- 创建 Canal 用户(注意:这里应该使用与后续配置一致的密码)
CREATE USER IF NOT EXISTS 'canal'@'192.168.192.%' IDENTIFIED BY 'canal';
GRANT ALL PRIVILEGES ON canal_manager.* TO 'canal'@'192.168.192.%';
-- 刷新权限
FLUSH PRIVILEGES;
-- 验证用户创建
SELECT User, Host FROM mysql.user WHERE User = 'canal';
USE canal_manager;
-- 创建 Canal Admin 会自动创建的表结构
quit;
重要提示:确保 Canal Admin 服务器能够访问 MySQL,测试连接:
# 在 Canal Admin 服务器上测试数据库连接
mysql -h192.168.192.170 -ucanal -p -e "SELECT 1;"
3.5 配置 Canal Admin
在 Canal Admin 服务器(192.168.192.180)上执行:
# 切换到 canal 用户
sudo -u canal bash
# 编辑配置文件
cat > /opt/canal-admin/conf/application.yml << 'EOF'
server:
port: 8089
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
spring.datasource:
# 注意:这里应该是你的 MySQL 主库 IP 地址
address: 192.168.192.170:3306
database: canal_manager
username: canal
password: canal
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${spring.datasource.address}/${spring.datasource.database}?useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true
hikari:
maximum-pool-size: 30
minimum-idle: 1
canal:
adminUser: admin
management:
endpoints:
web:
exposure:
include: health,info,metrics
EOF
# 验证配置文件语法
cat /opt/canal-admin/conf/application.yml
# 退出 canal 用户
exit
重要检查点:
- 确保
address配置的是正确的 MySQL 服务器 IP - 确保用户名密码与前面创建的数据库用户一致
- 使用
com.mysql.cj.jdbc.Driver新版驱动类名
3.6 启动 Canal Admin
在 Canal Admin 服务器(192.168.192.180)上执行:
# 切换到 canal 用户
sudo -u canal bash
# 进入 Canal Admin 目录
cd /opt/canal-admin
# 启动 Canal Admin
sh bin/startup.sh
# 退出 canal 用户
exit
3.7 配置 Systemd 服务
在 Canal Admin 服务器(192.168.192.180)上执行:
# 创建 Systemd 服务文件
cat <<EOF | sudo tee /etc/systemd/system/canal-admin.service
[Unit]
Description=Canal Admin
After=network.target mysql.service
[Service]
Type=forking
User=canal
Group=canal
Environment="JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64"
Environment="CANAL_ADMIN_HEAP_OPTS=-Xms1g -Xmx1g"
ExecStart=/opt/canal-admin/bin/startup.sh
ExecStop=/opt/canal-admin/bin/stop.sh
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
# 重载 Systemd
sudo systemctl daemon-reload
# 启用开机自启
sudo systemctl enable canal-admin
# 启动服务
sudo systemctl start canal-admin
# 查看服务状态
sudo systemctl status canal-admin
# 查看详细日志
sudo journalctl -u canal-admin -f
3.8 访问 Canal Admin
在浏览器中访问 Canal Admin:
http://192.168.192.180:8089
默认用户名密码:
- 用户名:
admin - 密码:
123456
首次登录后请修改密码。
集群配置=》集群名称:canal-cluster =》 集群配置:192.168.192.191:2181,192.168.192.192:2181,192.168.192.193:2181 集群模版:
#################################################
######### common argument #############
#################################################
# tcp bind ip
canal.ip =
# register ip to zookeeper
canal.register.ip =
canal.port = 11111
canal.metrics.pull.port = 11112
# canal instance user/passwd
# canal.user = canal
# canal.passwd = E3619321C1A937C46A0D8BD1DAC39F93B27D4458
# canal admin config
canal.admin.manager = 192.168.192.180:8089
canal.admin.port = 11110
canal.admin.user = admin
canal.admin.passwd = 14c33c244faa5ad403e38be03037060e97bc7aea
# admin auto register
canal.admin.register.auto = true
canal.admin.register.cluster = canal-cluster
#canal.admin.register.name =
# 注意:Canal Admin集群模式下必须配置Zookeeper地址
# 即使Kafka使用KRaft模式,Canal Admin仍需要Zookeeper进行集群协调
canal.zkServers = 192.168.192.191:2181,192.168.192.192:2181,192.168.192.193:2181
# flush data to zk
# canal持久化数据到zookeeper上的更新频率,单位毫秒
canal.zookeeper.flush.period = 1000
canal.withoutNetty = false
# tcp, kafka, rocketMQ, rabbitMQ
canal.serverMode = kafka
# flush meta cursor/parse position to file
# canal持久化数据到file上的目录
canal.file.data.dir = ${canal.conf.dir}
canal.file.flush.period = 1000
## memory store RingBuffer size, should be Math.pow(2,n)
# canal内存store中可缓存buffer记录数,需要为2的指数
canal.instance.memory.buffer.size = 16384
## memory store RingBuffer used memory unit size , default 1kb
canal.instance.memory.buffer.memunit = 1024
## meory store gets mode used MEMSIZE or ITEMSIZE
# canal内存store中数据缓存模式
# 1. ITEMSIZE : 根据buffer.size进行限制,只限制记录的数量
# 2. MEMSIZE : 根据buffer.size * buffer.memunit的大小,限制缓存记录的大小
canal.instance.memory.batch.mode = MEMSIZE
canal.instance.memory.rawEntry = true
## detecing config
# 是否开启心跳检查
canal.instance.detecting.enable = false
#canal.instance.detecting.sql = insert into retl.xdual values(1,now()) on duplicate key update x=now()
# 心跳检查sql
canal.instance.detecting.sql = select 1
# 心跳检查频率,单位秒
canal.instance.detecting.interval.time = 3
# 心跳检查失败重试次数
canal.instance.detecting.retry.threshold = 3
# 非常注意:interval.time * retry.threshold值,应该参考既往DBA同学对数据库的故障恢复时间,
# “太短”会导致集群运行态角色“多跳”;“太长”失去了活性检测的意义,导致集群的敏感度降低,Consumer断路可能性增加。
# 心跳检查失败后,是否开启自动mysql自动切换
canal.instance.detecting.heartbeatHaEnable = false
# 说明:比如心跳检查失败超过阀值后,如果该配置为true,canal就会自动链到mysql备库获取binlog数据
# support maximum transaction size, more than the size of the transaction will be cut into multiple transactions delivery
# 支持最大事务大小,将超过事务大小的事务切成多个事务交付
canal.instance.transaction.size = 1024
# mysql fallback connected to new master should fallback times
# canal发生mysql切换时,在新的mysql库上查找 binlog时需要往前查找的时间,单位秒
canal.instance.fallbackIntervalInSeconds = 60
# 说明:mysql主备库可能存在解析延迟或者时钟不统一,需要回退一段时间,保证数据不丢
# network config
# 网络链接参数,SocketOptions.SO_RCVBUF
canal.instance.network.receiveBufferSize = 16384
# 网络链接参数,SocketOptions.SO_SNDBUF
canal.instance.network.sendBufferSize = 16384
# 网络链接参数,SocketOptions.SO_TIMEOUT
canal.instance.network.soTimeout = 30
# binlog filter config
canal.instance.filter.druid.ddl = true
# ddl语句是否隔离发送,开启隔离可保证每次只返回发送一条ddl数据,不和其他dml语句混合返回.(otter ddl同步使用)
canal.instance.filter.query.dcl = true
# 是否忽略DML的query语句,比如insert/update/delete table.(mysql5.6的ROW模式可以包含statement模式的query记录)
canal.instance.filter.query.dml = true
# 是否忽略DDL的query语句,比如create table/alater table/drop table/rename table/create index/drop index. (目前支持的ddl类型主要为table级别的操作,create databases/trigger/procedure暂时划分为dcl类型)
canal.instance.filter.query.ddl = true
# 注意:上面三个参数默认都是false 如无必要建议设置为true,否则后面你过滤表的时候可能会出现不生效的情况
anal.instance.filter.table.error = true
canal.instance.filter.rows = false
# 把事务头尾过滤
canal.instance.filter.transaction.entry = true
canal.instance.filter.dml.insert = false
canal.instance.filter.dml.update = false
canal.instance.filter.dml.delete = false
# binlog format/image check
#canal.instance.binlog.format = ROW,STATEMENT,MIXED
canal.instance.binlog.format = ROW,STATEMENT,MIXED
canal.instance.binlog.image = FULL,MINIMAL,NOBLOB
# binlog ddl isolation
canal.instance.get.ddl.isolation = false
# parallel parser config
canal.instance.parser.parallel = true
## concurrent thread number, default 60% available processors, suggest not to exceed Runtime.getRuntime().availableProcessors()
#canal.instance.parser.parallelThreadSize = 16
## disruptor ringbuffer size, must be power of 2
canal.instance.parser.parallelBufferSize = 256
# table meta tsdb info #关于时间序列版本
canal.instance.tsdb.enable = true
canal.instance.tsdb.dir = ${canal.file.data.dir:../conf}/${canal.instance.destination:}
canal.instance.tsdb.url = jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
canal.instance.tsdb.dbUsername = canal
canal.instance.tsdb.dbPassword = canal
# dump snapshot interval, default 24 hour
canal.instance.tsdb.snapshot.interval = 24
# purge snapshot expire , default 360 hour(15 days)
canal.instance.tsdb.snapshot.expire = 360
#################################################
######### destinations #############
#################################################
canal.destinations =
# conf root dir
canal.conf.dir = ../conf
# auto scan instance dir add/remove and start/stop instance
# 如果配置为true,canal.conf.dir目录下的instance配置变化会自动触发
# a. instance目录新增: 触发instance配置载入,lazy为true时则自动启动
# b. instance目录删除:卸载对应instance配置,如已启动则进行关闭
# c. instance.properties文件变化:reload instance配置,如已启动自动进行重启操作
# 开启instance自动扫描
canal.auto.scan = true
# instance自动扫描的间隔时间,单位秒
canal.auto.scan.interval = 5
# set this value to 'true' means that when binlog pos not found, skip to latest.
# WARN: pls keep 'false' in production env, or if you know what you want.
canal.auto.reset.latest.pos.mode = false
canal.instance.tsdb.spring.xml = classpath:spring/tsdb/h2-tsdb.xml
#canal.instance.tsdb.spring.xml = classpath:spring/tsdb/mysql-tsdb.xml
canal.instance.global.mode = manager
# 全局lazy模式
canal.instance.global.lazy = false
canal.instance.global.manager.address = ${canal.admin.manager}
#canal.instance.global.spring.xml = classpath:spring/memory-instance.xml
#canal.instance.global.spring.xml = classpath:spring/file-instance.xml
# 此处建议设置为classpath:spring/default-instance.xml
canal.instance.global.spring.xml = classpath:spring/default-instance.xml
##################################################
######### MQ Properties #############
##################################################
# aliyun ak/sk , support rds/mq
canal.aliyun.accessKey =
canal.aliyun.secretKey =
canal.aliyun.uid=
canal.mq.flatMessage = true
canal.mq.canalBatchSize = 50
canal.mq.canalGetTimeout = 100
# Set this value to "cloud", if you want open message trace feature in aliyun.
canal.mq.accessChannel = local
canal.mq.database.hash = true
canal.mq.send.thread.size = 30
canal.mq.build.thread.size = 8
##################################################
######### Kafka #############
##################################################
# kafka bootstrap 信息,可以不填所有的
kafka.bootstrap.servers = 192.168.192.211:9092,192.168.192.212:9092,192.168.192.213:9092
kafka.acks = all
kafka.compression.type = none
kafka.batch.size = 16384
# 建议设为50
kafka.linger.ms = 50
kafka.max.request.size = 1048576
kafka.buffer.memory = 33554432
kafka.max.in.flight.requests.per.connection = 1
kafka.retries = 0
kafka.kerberos.enable = false
kafka.kerberos.krb5.file = "../conf/kerberos/krb5.conf"
kafka.kerberos.jaas.file = "../conf/kerberos/jaas.conf"
##################################################
######### RocketMQ #############
##################################################
rocketmq.producer.group = test
rocketmq.enable.message.trace = false
rocketmq.customized.trace.topic =
rocketmq.namespace =
rocketmq.namesrv.addr = 127.0.0.1:9876
rocketmq.retry.times.when.send.failed = 0
rocketmq.vip.channel.enabled = false
rocketmq.tag =
##################################################
######### RabbitMQ #############
##################################################
rabbitmq.host =
rabbitmq.virtual.host =
rabbitmq.exchange =
rabbitmq.username =
rabbitmq.password =
rabbitmq.deliveryMode =
4. Canal Server 部署
4.1 环境准备
在两台 Canal Server 服 务器(192.168.192.181、192.168.192.182)上执行:
4.1.1 安装 Java
# 更新软件包列表
sudo apt update
# 安装 OpenJDK 11
sudo apt install -y openjdk-11-jdk
# 验证安装
java -version
# 设置 JAVA_HOME 环境变量
echo 'export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64' | sudo tee -a /etc/environment
source /etc/environment