Canal 是阿里巴巴开源的一款基于 MySQL 数据库增量日志(binlog)的实时数据同步工具,主要用于数据库变更的订阅与消费。它可以模拟 MySQL Slave 的交互协议,解析 binlog 事件并推送变更数据,适用于数据同步、缓存更新、实时分析等场景。
Canal 核心概念
-
工作原理:
-
Canal 伪装成 MySQL 从库(Slave),向主库(Master)发送 dump 请求。
-
MySQL 主库收到请求后,推送 binlog 事件给 Canal。
-
Canal 解析 binlog 并转发数据变更事件(INSERT/UPDATE/DELETE)到下游消费者(如你的 Spring Boot 应用)。
-
-
核心组件:
-
Server:Canal 服务端,负责连接 MySQL、解析 binlog。
-
Client:客户端,订阅 Canal 服务端的数据变更事件。
-
Instance:Canal 实例,每个实例对应一个 MySQL 数据库的同步任务。
-
在 Spring Boot 中使用 Canal 的步骤
1. 准备工作
-
开启 MySQL binlog:
-
确保 MySQL 的
my.cnf
配置中启用 binlog,格式为ROW
:[mysqld] log-bin=mysql-bin binlog-format=ROW server-id=1
-
创建 Canal 专用用户并授权:
CREATE USER 'canal'@'%' IDENTIFIED BY 'canal'; GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%'; FLUSH PRIVILEGES;
-
-
部署 Canal Server:
-
下载 Canal 服务端(Canal Release)。
-
解压后修改
conf/example/instance.properties
:canal.instance.master.address=127.0.0.1:3306 # MySQL地址 canal.instance.dbUsername=canal canal.instance.dbPassword=canal canal.instance.filter.regex=.*\\..* # 监控所有库表
-
启动 Canal Server:
sh bin/startup.sh
-
2. Spring Boot 集成 Canal 客户端
(1) 添加依赖
Canal 官方未提供官方客户端,可使用第三方库(如 com.alibaba.otter:canal.client
):
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client</artifactId>
<version>1.1.6</version>
</dependency>
(2) 配置 Canal 客户端
@Configuration
public class CanalConfig {
@Value("${canal.server.host}")
private String canalHost;
@Value("${canal.server.port}")
private int canalPort;
@Value("${canal.destination}")
private String destination;
@Bean
public CanalConnector canalConnector() {
// 创建 Canal 连接器(支持集群模式)
CanalConnector connector = CanalConnectors.newClusterConnector(
Lists.newArrayList(new InetSocketAddress(canalHost, canalPort)),
destination, "", "");
connector.connect();
connector.subscribe(".*\\..*"); // 订阅所有表
return connector;
}
}
(3) 监听并处理数据变更
@Component
public class CanalEventListener {
@Autowired
private CanalConnector canalConnector;
@PostConstruct
public void startListening() {
new Thread(() -> {
while (true) {
try {
Message message = canalConnector.getWithoutAck(100); // 批量获取数据
long batchId = message.getId();
if (batchId != -1 && !message.getEntries().isEmpty()) {
processEntries(message.getEntries());
}
canalConnector.ack(batchId); // 确认消费
} catch (Exception e) {
canalConnector.rollback(); // 处理失败,回滚
}
}
}).start();
}
private void processEntries(List<CanalEntry.Entry> entries) {
for (CanalEntry.Entry entry : entries) {
if (entry.getEntryType() == CanalEntry.EntryType.ROWDATA) {
CanalEntry.RowChange rowChange;
try {
rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException("解析数据失败", e);
}
CanalEntry.EventType eventType = rowChange.getEventType();
String tableName = entry.getHeader().getTableName();
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
// 处理变更数据
if (eventType == CanalEntry.EventType.INSERT) {
handleInsert(rowData, tableName);
} else if (eventType == CanalEntry.EventType.UPDATE) {
handleUpdate(rowData, tableName);
} else if (eventType == CanalEntry.EventType.DELETE) {
handleDelete(rowData, tableName);
}
}
}
}
}
private void handleInsert(CanalEntry.RowData rowData, String tableName) {
List<CanalEntry.Column> columns = rowData.getAfterColumnsList();
// 提取字段并执行业务逻辑(如更新缓存、同步到ES等)
}
// 类似地实现 handleUpdate 和 handleDelete
}
3. 处理数据变更的业务逻辑
根据业务需求,将变更数据同步到其他系统(如 Redis、Elasticsearch 或发送消息队列):
private void handleUpdate(CanalEntry.RowData rowData, String tableName) {
Map<String, String> before = rowData.getBeforeColumnsList().stream()
.collect(Collectors.toMap(CanalEntry.Column::getName, CanalEntry.Column::getValue));
Map<String, String> after = rowData.getAfterColumnsList().stream()
.collect(Collectors.toMap(CanalEntry.Column::getName, CanalEntry.Column::getValue));
if ("user".equals(tableName)) {
String userId = after.get("id");
// 更新缓存中的用户信息
redisTemplate.opsForValue().set("user:" + userId, after);
}
}
注意事项
-
确保 MySQL binlog 配置正确,格式必须为
ROW
。 -
处理网络中断:Canal 客户端需具备重连机制。
-
批量消费与ACK机制:合理设置批量大小,及时确认消息(
ack
)或回滚(rollback
)。 -
监控 Canal 服务:通过 Canal 提供的管理端口(默认 11111)监控同步状态。
通过以上步骤,你可以在 Spring Boot 项目中实现基于 Canal 的实时数据同步。