一、Canal 核心原理详解
Canal 是阿里巴巴开源的数据同步工具,基于 MySQL 数据库的 binlog 增量订阅&消费 机制实现数据同步。其核心原理如下:
1. MySQL Binlog 机制
- Binlog 作用:MySQL 的二进制日志,记录所有数据变更(INSERT/UPDATE/DELETE)
- 三种格式 :
- Statement:记录 SQL 语句
- Row:记录行级变更(Canal 使用此格式)
- Mixed:混合模式
- Row 格式优势:包含变更前后的完整数据行,无 SQL 解析歧义
2. Canal 工作原理
graph LR
A[MySQL Master] -->|推送 binlog| B[Canal Server]
B -->|解析 binlog| C[Canal Client]
C --> D[Elasticsearch]
- 伪装 Slave :
- Canal 启动后伪装成 MySQL Slave
- 向 Master 发送
dump
协议请求 binlog
- Binlog 接收 :
- MySQL Master 收到请求后推送 binlog
- 协议解析 :
- Canal 解析 binlog 原始字节流
- 转换为结构化
Entry
对象(含 Table/Schema 信息)
- 事件转换 :
- 将
Entry
转换为可消费的Message
对象
- 将
- 数据投递 :
- 客户端订阅并处理消息(写入 ES)
3. 关键组件
组件 | 功能说明 |
---|---|
EventParser | 从数据源抓取并解析 binlog |
EventSink | 数据过滤&加工(如库表路由) |
EventStore | 数据存储(内存/PG) |
MetaManager | 元数据管理(同步点位持久化) |
二、数据同步全流程实现
阶段 1:MySQL 准备(必须配置)
sql
-- 检查 binlog 配置
SHOW VARIABLES LIKE 'log_bin'; -- 必须 ON
SHOW VARIABLES LIKE 'binlog_format'; -- 必须 ROW
-- 创建 Canal 专用账号
CREATE USER 'canal'@'%' IDENTIFIED BY 'canal_password';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
FLUSH PRIVILEGES;
阶段 2:Canal Server 部署
配置示例 (conf/example/instance.properties
)
properties
# MySQL 连接配置
canal.instance.master.address=127.0.0.1:3306
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal_password
canal.instance.connectionCharset=UTF-8
# 过滤规则(同步哪些数据)
canal.instance.filter.regex=.*\\..* # 所有库表
# 或指定商城表:mall_db\\.product,mall_db\\.sku
# Binlog 位置存储策略(默认内存)
canal.instance.tsdb.enable=true # 启用持久化存储
阶段 3:首次全量同步方案
sequenceDiagram
participant A as 应用服务
participant C as Canal
participant M as MySQL
participant E as ES
A->>C: 触发全量同步指令
C->>M: 记录当前 binlog 位置 POS-1
C->>M: SELECT * FROM products
loop 分批读取
M-->>C: 批量数据
C->>E: Bulk 导入 ES
end
C->>C: 保存 POS-1 到元数据
Note right of C: 增量阶段从 POS-1 开始
关键实现要点:
-
使用 DataSource 直连 MySQL :
javatry (Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) { stmt.setFetchSize(5000); // 分批获取 ResultSet rs = stmt.executeQuery("SELECT id,name,price FROM products"); while (rs.next()) { // 构建 ES 文档 } }
-
ES 批量写入 :
javaBulkRequest bulkRequest = new BulkRequest(); for (Product product : productList) { IndexRequest request = new IndexRequest("products") .id(product.getId().toString()) .source(JSON.toJSONString(product), XContentType.JSON); bulkRequest.add(request); } restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
阶段 4:增量同步流程
graph TB
S[Canal 捕获 binlog] --> P{事件类型}
P -->|INSERT| I[生成 ES IndexRequest]
P -->|UPDATE| U[生成 ES UpdateRequest]
P -->|DELETE| D[生成 ES DeleteRequest]
I --> B[ES Bulk 提交]
U --> B
D --> B
Canal Client 核心代码:
java
CanalConnector connector = CanalConnectors.newClusterConnector(
"192.168.1.100:11111", "example", "", "");
connector.connect();
connector.subscribe("mall_db\\..*"); // 订阅商城库
while (running) {
Message message = connector.getWithoutAck(100); // 批量获取
List<CanalEntry.Entry> entries = message.getEntries();
for (CanalEntry.Entry entry : entries) {
if (entry.getEntryType() == EntryType.ROWDATA) {
RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
for (RowData rowData : rowChange.getRowDatasList()) {
processData(rowData, entry.getHeader().getTableName());
}
}
}
connector.ack(message.getId()); // 确认消费
}
ES 文档处理逻辑:
java
void processData(RowData rowData, String tableName) {
if ("products".equals(tableName)) {
if (eventType == INSERT || eventType == UPDATE) {
Map<String, String> afterColumns = rowData.getAfterColumnsList().stream()
.collect(Collectors.toMap(Column::getName, Column::getValue));
IndexRequest request = new IndexRequest("products")
.id(afterColumns.get("id"))
.source(afterColumns);
esClient.index(request); // 实际用 bulk
} else if (eventType == DELETE) {
Map<String, String> beforeColumns = rowData.getBeforeColumnsList().stream()
.collect(Collectors.toMap(Column::getName, Column::getValue));
DeleteRequest request = new DeleteRequest("products")
.id(beforeColumns.get("id"));
esClient.delete(request);
}
}
}
三、商品搜索系统整合
1. Elasticsearch 索引优化
json
PUT /products
{
"settings": {
"analysis": {
"analyzer": {
"ik_smart_pinyin": {
"type": "custom",
"tokenizer": "ik_smart",
"filter": ["pinyin_filter"]
}
},
"filter": {
"pinyin_filter": {
"type": "pinyin",
"keep_first_letter": true
}
}
}
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "ik_smart_pinyin", // 中文+拼音混合搜索
"fields": {
"keyword": {"type": "keyword"}
}
},
"price": {"type": "double"},
"category_id": {"type": "integer"},
"create_time": {"type": "date"}
}
}
}
2. 搜索接口实现
java
public SearchResponse searchProducts(String keyword, Double minPrice, Double maxPrice) {
SearchRequest searchRequest = new SearchRequest("products");
// 构建布尔查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 关键词查询(名称+分类+品牌)
if (StringUtils.isNotBlank(keyword)) {
MultiMatchQueryBuilder keywordQuery = QueryBuilders.multiMatchQuery(keyword)
.field("name", 10) // 权重提升
.field("category_name")
.field("brand_name");
boolQuery.must(keywordQuery);
}
// 价格区间过滤
if (minPrice != null || maxPrice != null) {
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
if (minPrice != null) rangeQuery.gte(minPrice);
if (maxPrice != null) rangeQuery.lte(maxPrice);
boolQuery.filter(rangeQuery);
}
// 分页排序
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
.query(boolQuery)
.from(0).size(10)
.sort("_score", SortOrder.DESC) // 相关性
.sort("create_time", SortOrder.DESC); // 新品优先
return restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
}
四、生产环境注意事项
-
性能优化:
- ES 批量写入:建议每批次 500-1000 个文档
- Canal 内存管理:调整
canal.instance.memory.buffer.size
(默认 16MB) - 网络压缩:启用
canal.instance.network.compression = SNAPPY
-
高可用方案:
graph LR M[MySQL Master] -->|binlog| C1[Canal Server 1] M -->|binlog| C2[Canal Server 2] C1 --> K[Kafka] C2 --> K K --> E[ES Consumer Group] -
数据一致性保障:
- 全量同步时暂停增量(记录 binlog position)
- 使用 ES 版本号冲突检测(
version_type=external
) - 定期校验 MySQL 与 ES 数据差异
-
监控指标:
- Canal:解析延迟(
canal_parse_time
) - ES:索引速率(
indexing_pressure
) - MySQL:Binlog 堆积量(
binlog_size
)
- Canal:解析延迟(
故障恢复流程:
- 检查 Canal 元数据中的 binlog position
- 对比 MySQL
SHOW MASTER STATUS
- 若存在差异,回退 position 重新同步
- 触发指定时间段的数据补偿
通过以上设计,可实现毫秒级搜索数据同步,支持每日千万级商品更新场景,查询响应时间控制在 50ms 内。