1. 数据一致性问题的本质
在分布式系统中保持MySQL和Elasticsearch的数据一致性,本质上是在解决两个不同技术栈之间的数据同步问题。MySQL作为关系型数据库提供ACID事务保证,而Elasticsearch作为搜索引擎更注重查询性能,两者的设计哲学差异导致了同步挑战。
我处理过的一个电商系统案例中,商品信息在MySQL中更新后,ES中的搜索结果显示旧价格长达30分钟,直接导致客户投诉。这种延迟问题在订单状态同步、库存更新等场景尤为致命。
2. 主流同步方案对比
2.1 双写模式
直接在业务代码中同时写入MySQL和ES。这是最直观的方案,但存在严重缺陷:
java复制// 典型双写伪代码示例
public void updateProduct(Product product) {
// 先写MySQL
productMapper.update(product);
// 再写ES
esClient.update(product);
}
致命问题:第二步ES写入失败时,系统状态不一致且难以恢复。我曾见过某金融系统因此导致对账差异累计达百万级。
2.2 定时同步
通过定时任务扫描MySQL的update_time字段同步变更数据。这种方式实现简单:
sql复制-- 每小时同步一次
SELECT * FROM products
WHERE update_time > '2023-07-20 12:00:00';
但存在同步延迟大(取决于任务间隔)、全表扫描压力大等问题。适合对实时性要求不高的后台报表场景。
2.3 基于Binlog的同步
这是目前最成熟的方案,通过解析MySQL的binlog获取精确变更事件。整体架构:
code复制MySQL -> Canal/Maxwell -> Kafka -> Logstash/自定义消费者 -> ES
3. 基于Binlog的完整实现方案
3.1 环境搭建
使用Canal作为binlog解析器:
yaml复制# canal.properties关键配置
canal.instance.mysql.slaveId = 1234
canal.instance.filter.regex = .*\\..*
canal.mq.topic=es_sync
3.2 消息队列选型
Kafka是最可靠的选择,需要配置:
bash复制# 创建专门topic
bin/kafka-topics.sh --create \
--topic es_sync \
--partitions 3 \
--replication-factor 2 \
--bootstrap-server localhost:9092
3.3 消费者实现要点
处理消息时需要特别注意:
java复制// 伪代码示例
public void handleMessage(Message message) {
try {
// 幂等处理
if(esClient.exists(message.id)) {
return;
}
// 批量写入提升性能
bulkRequest.add(new IndexRequest("index")
.id(message.id)
.source(message.data));
// 每100条或1秒执行一次
if(bulkRequest.size() >= 100 || timeout()) {
esClient.bulk(bulkRequest);
bulkRequest = new BulkRequest();
}
} catch (Exception e) {
// 死信队列处理
sendToDLQ(message);
}
}
4. 一致性保障的进阶策略
4.1 延迟监控方案
部署监控系统检测同步延迟:
python复制# 监控脚本示例
mysql_time = execute_sql("SELECT MAX(update_time) FROM products")
es_time = es.search(index="products", body={"aggs":{"max_time":{"max":{"field":"update_time"}}}})
if (mysql_time - es_time) > timedelta(minutes=1):
alert("ES同步延迟超过阈值!")
4.2 补偿机制设计
当监控发现不一致时触发补偿:
- 记录不一致的ID范围
- 从MySQL查询这些记录的最新状态
- 批量更新到ES
- 验证数据一致性
4.3 灰度发布方案
新数据模型上线时采用双写并行:
- 旧字段继续同步
- 新字段同步到ES的临时索引
- 验证无误后切换别名
- 下线旧字段
5. 典型问题排查指南
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| ES数据缺失 | Canal连接断开 | 检查canal.log网络错误 |
| 字段映射失败 | 类型不匹配 | 重建索引并指定mapping |
| 同步延迟高 | Kafka积压 | 增加消费者并发度 |
| 内存溢出 | 批量太大 | 调整bulk.size到500-1000 |
6. 性能优化实战经验
-
批量大小:ES的bulk API在5-15MB负载时性能最佳,需要根据文档平均大小计算:
code复制单条文档大小 = 2KB 理想批量数 = 10MB / 2KB ≈ 5000条 -
索引策略:
- 冷热数据分离
- 基于时间创建滚动索引
- 关闭不需要的字段索引
-
硬件配置:
- 每个ES数据节点配置SSD磁盘
- 独立部署专用协调节点
- JVM堆内存不超过32GB
7. 特殊场景处理
删除同步方案:
sql复制-- MySQL中采用逻辑删除
UPDATE products SET is_deleted=1 WHERE id=123;
-- ES中通过filter排除
{
"query": {
"bool": {
"must_not": {"term": {"is_deleted": 1}}
}
}
}
关联表同步:
对于多表关联数据,建议:
- 在MySQL侧预先组装好宽表
- 通过视图提供给Canal解析
- 或者使用Elasticsearch的nested类型
8. 生产环境检查清单
部署前必须验证:
- [ ] 网络延迟:数据中心间RTT应<5ms
- [ ] 权限配置:Canal账号需有REPLICATION权限
- [ ] 字段映射:所有关键字段建立正确mapping
- [ ] 监控报警:配置延迟、错误率监控
- [ ] 回滚方案:准备索引快照恢复流程
这套方案在某跨境电商平台实现了99.99%的数据一致性,同步延迟控制在5秒内。核心在于建立可靠的监控体系,确保能快速发现并修复不一致情况。