概述:
Canal 是由阿里巴巴开源的一个基于 MySQL 数据库增量日志解析的数据同步工具,主要用于将 MySQL 的数据变更实时同步到其他中间件、数据库或系统中。Canal 模拟了 MySQL Slave 的交互协议,伪装成一个 Slave 连接到 MySQL Master 服务器,从而可以读取到 MySQL binlog 的变更数据,并解析成自己的数据结构。
Canal 的工作原理:
-
模拟 Slave: Canal 伪装成 MySQL Slave,向 MySQL Master 发起连接请求。
-
获取 Binlog: Canal 通过建立的连接,请求并获取 MySQL Master 的 Binlog。
-
解析 Binlog: Canal 解析 Binlog 中的数据变更信息,转换为自己的数据格式(如 Java 对象)。
-
数据传输: Canal 将解析后的数据以一定的格式(如 Protobuf、JSON)提供给订阅者。
-
数据消费: 订阅者(可以是 Canal 客户端或其他系统)消费这些数据,进行相应的数据同步或业务逻辑处理。
Canal 的优点:
-
实时性: 可以实现近乎实时的数据同步。
-
低侵入性: 不需要修改 MySQL 源码,只需要开启 MySQL 的 Binlog 日志功能。
-
高可用性: 支持集群部署,通过 Zookeeper 管理多个 Canal 实例,提高系统的可用性。
-
灵活性: 提供了简单的客户端 API,可以灵活地接入自定义的数据处理逻辑。
-
兼容性: 支持多种数据消费方式,如 Kafka、RocketMQ 等。
-
数据不丢失: 提供了数据位置标记,确保数据同步的一致性和可靠性。
Canal 的缺点:
-
学习成本: 对于初学者来说,理解 Canal 的工作原理和配置可能需要一定的学习成本。
-
依赖 MySQL 特性: 依赖于 MySQL 的 Binlog 日志,如果 MySQL 实例未开启 Binlog 日志或配置不正确,Canal 将无法工作。
-
性能开销: 对于写入量非常大的数据库,Canal 监听和解析 Binlog 可能会带来额外的性能开销。
-
异常处理: 在网络抖动或 MySQL Master 异常情况下,需要合理处理异常和重连策略,以免影响数据同步。
-
数据一致性: 如果 Binlog 日志在某些特定情况下丢失,可能会导致数据不一致的问题。
-
维护成本: 需要对 Canal 进行适当的监控和维护,以确保其稳定运行。
流程图:
graph LR
A[MySQL Server] -->|Binlog| B[Canal Server]
B -->|Parse Binlog| C{Canal Connector}
C -->|Get Changes| D[Canal Client]
D -->|Data Synchronization| E[Data Consumers]
E --> F[Database/Cache/Message Queue]
E --> G[Search Engine]
E --> H[Other Systems]
"MySQL Server" 产生 Binlog。
- "Canal Server" 连接到 MySQL Server 并读取 Binlog。
- "Canal Connector" 是 Canal Server 的一部分,解析 Binlog 并提供给客户端。
- "Canal Client" 从 Canal Connector 获取变更数据。
- "Data Consumers" 是数据消费者,可能是数据库、缓存、消息队列、搜索引擎或其他系统。
- 数据消费者处理同步的数据,更新自己的数据状态。
时序图:
sequenceDiagram
participant MySQL
participant Canal Server
participant Canal Client
participant Consumer
MySQL->>Canal Server: Write Binlog
Canal Server->>Canal Server: Read Binlog
Canal Server->>Canal Client: Send Parsed Data
Canal Client->>Consumer: Apply Data Changes
Consumer->>Consumer: Process Data
- "MySQL" 代表 MySQL 服务器,它负责写入 Binlog。
- "Canal Server" 代表 Canal 服务器,它从 MySQL 读取 Binlog 并将其解析成数据变更。
- "Canal Client" 代表连接到 Canal Server 的客户端,它接收来自 Canal Server 的数据变更。
- "Consumer" 代表数据消费者,它从 Canal Client 接收数据变更,并根据业务逻辑进行处理。
时序图展示了从 MySQL 服务器写入 Binlog 开始,到数据消费者处理数据变更的整个流程。
总体来说,Canal 是一个成熟的工具,适用于需要实时数据同步的场景,尤其是在构建数据仓库、实现缓存更新、搜索引擎索引更新等场合。不过,使用 Canal 时也需要考虑其对系统的影响,并做好相应的异常处理和监控策略。
Spring Boot 中整合 Canal
Spring Boot 中整合 Canal 来同步 MySQL 数据,需要按照以下步骤操作:
1. 添加 Canal 客户端依赖:
在项目的 pom.xml
或 build.gradle
文件中添加 Canal 客户端依赖。
Maven 依赖示例:
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client</artifactId>
<version>1.1.5</version>
</dependency>
Gradle 依赖示例:
implementation 'com.alibaba.otter:canal.client:1.1.5' // 使用最新的版本
2. 配置 Canal 连接属性:
在 application.properties
或 application.yml
中添加 Canal 的配置。
# application.properties
canal.host=127.0.0.1
canal.port=11111
canal.destination=example
canal.username=
canal.password=
3. 创建 Canal 客户端配置:
创建一个配置类来配置 Canal 客户端。
@Configuration
public class CanalConfig {
@Value("${canal.host}")
private String host;
@Value("${canal.port}")
private int port;
@Value("${canal.destination}")
private String destination;
@Value("${canal.username}")
private String username;
@Value("${canal.password}")
private String password;
@Bean
public CanalConnector canalConnector() {
// 创建连接器
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(host, port),
destination, username, password);
return connector;
}
}
4. 创建 Canal 客户端监听器:
创建一个服务类来监听 Canal 服务器的变化,并处理同步逻辑。
@Component
public class CanalClient {
@Autowired
private CanalConnector canalConnector;
@PostConstruct
public void listen() {
Executors.newSingleThreadExecutor().submit(() -> {
canalConnector.connect();
canalConnector.subscribe(".*\\..*");
canalConnector.rollback();
try {
while (true) {
Message message = canalConnector.getWithoutAck(1000);
long batchId = message.getId();
int size = message.getEntries().size();
if (batchId != -1 && size > 0) {
handleEntries(message.getEntries());
}
canalConnector.ack(batchId); // 提交确认
}
} finally {
canalConnector.disconnect();
}
});
}
private void handleEntries(List<CanalEntry.Entry> entries) {
for (CanalEntry.Entry entry : entries) {
// 根据实际业务处理逻辑
}
}
}
针对以上代码可以进一步优化一下:
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CanalConfig {
// ... 同上,配置类代码
}
@Service
public class CanalClientService implements DisposableBean {
private final CanalConnector connector;
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
public CanalClientService(CanalConnector connector) {
this.connector = connector;
}
@PostConstruct
public void init() {
// 使用线程池启动监听
executorService.submit(this::process);
}
@Async
public void process() {
try {
connector.connect();
connector.subscribe(".*\\..*");
while (true) {
// 获取数据并处理
// ... 同上,数据获取和处理代码
}
} catch (Exception e) {
// 日志记录和重连逻辑
} finally {
connector.disconnect();
}
}
@Override
public void destroy() {
// 优雅关闭
connector.disconnect();
executorService.shutdown();
}
private void handleEntry(List<CanalEntry.Entry> entries) {
// 数据处理逻辑解耦到单独的服务或组件
// ... 同上,数据处理代码
}
}
使用了 ExecutorService
来异步执行 Canal 客户端监听工作,并在 destroy
方法中进行了资源的清理。同时,将配置信息外部化,并预留了异常处理和重连的位置。
5. 处理 Canal 监听到的数据变更:
在 handleEntries
方法中,需要解析来自 Canal 的 Entry
对象,并根据业务需求同步数据到应用程序或另一个数据库。
private void handleEntries(List<CanalEntry.Entry> entries) {
for (CanalEntry.Entry entry : entries) {
if (entry.getEntryType() == CanalEntry.EntryType.ROWDATA) {
CanalEntry.RowChange rowChange = null;
try {
rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
} catch (Exception e) {
throw new RuntimeException("ERROR ## parser of eromanga-event has an error, data:" + entry.toString(),
e);
}
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
if (rowChange.getEventType() == CanalEntry.EventType.DELETE) {
// 处理删除逻辑
} else if (rowChange.getEventType() == CanalEntry.EventType.INSERT) {
// 处理插入逻辑
} else {
// 处理更新逻辑
}
}
}
}
}
确保 MySQL 服务器和 Canal 服务器已经正确配置并且运行。在 MySQL 服务器上,需要开启 binlog,并设置 binlog_format=ROW
,Canal 才能捕获到数据变更。
此外,需要根据具体业务需求来实现数据同步的逻辑。这可能包括将数据写入另一个数据库、发送到消息队列、触发其他服务的操作等。