1. 数据一致性问题的本质
在分布式系统中保持MySQL和Elasticsearch(ES)的数据一致性,是每个中大型应用都会遇到的经典难题。我经历过一个电商项目,商品信息在MySQL中更新后,用户在前端搜索到的还是旧数据,这种延迟直接导致了客诉。本质上,这是两种数据库的天然差异造成的:
- MySQL:关系型数据库,强一致性设计,适合事务性操作
- Elasticsearch:搜索引擎,最终一致性设计,适合快速检索
当业务需要同时使用两者的优势时,就必须设计可靠的数据同步机制。根据CAP理论,我们必须在一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)之间做出取舍。
2. 主流同步方案对比
2.1 双写模式(Dual Write)
最直观的方案是在应用层同时写入两个数据库:
java复制// 伪代码示例
@Transactional
void updateProduct(Product product) {
// 写入MySQL
productMapper.update(product);
// 写入ES
esClient.update(product);
}
致命缺陷:
- 非原子操作,可能一个成功一个失败
- 网络抖动时会出现数据不一致
- 需要处理复杂的回滚逻辑
我在实际项目中踩过的坑:某次服务器负载过高,MySQL写入成功但ES超时,导致两周内5%的商品数据不一致,最终只能手动修复。
2.2 定时同步(Scheduled Sync)
通过定时任务全量/增量同步数据:
sql复制-- 每天凌晨同步
SELECT * FROM products WHERE update_time > '昨日时间';
局限性:
- 数据延迟可能达数小时
- 全量同步时性能压力大
- 需要维护复杂的同步状态
2.3 基于Binlog的CDC(Change Data Capture)
目前最成熟的方案,通过MySQL的二进制日志捕获变更:
code复制MySQL -> Canal/Debezium -> Kafka -> Elasticsearch
核心优势:
- 异步解耦,不影响主业务
- 毫秒级延迟
- 自动重试机制保障可靠性
3. 基于Binlog的完整实现方案
3.1 基础设施搭建
推荐技术栈组合:
- 采集器:Alibaba Canal (支持MySQL 5.6+/8.0)
- 消息队列:Apache Kafka(高吞吐)
- 消费者:自研或Logstash
yaml复制# Canal配置示例
canal.instance.mysql.slaveId = 1234
canal.instance.filter.regex = .*\\..*
3.2 消息格式设计
建议使用Avro序列化:
json复制{
"type": "UPDATE",
"database": "shop",
"table": "products",
"data": {
"id": 101,
"name": "新款手机"
},
"old_data": {
"name": "旧款手机"
}
}
3.3 幂等性处理
ES写入必须实现幂等:
java复制// 使用version控制实现幂等更新
IndexRequest request = new IndexRequest("products")
.id(productId)
.source(json, XContentType.JSON)
.setIfSeqNo(seqNo)
.setIfPrimaryTerm(primaryTerm);
4. 异常处理与监控
4.1 延迟监控
关键指标:
- 采集延迟(Canal到MySQL)
- 处理延迟(Kafka消费)
- 写入延迟(到ES)
bash复制# 监控Kafka积压
kafka-consumer-groups --bootstrap-server localhost:9092 \
--group es-sync --describe
4.2 死信队列处理
建议方案:
- 自动重试3次
- 失败消息存入DLQ
- 定时扫描DLQ人工处理
python复制# 伪代码示例
def process_message(msg):
try:
sync_to_es(msg)
except Exception as e:
if msg.retry < 3:
kafka.retry(msg)
else:
save_to_dlq(msg)
5. 高级优化技巧
5.1 字段映射优化
ES字段类型需要特别设计:
json复制{
"mappings": {
"properties": {
"price": { "type": "scaled_float", "scaling_factor": 100 },
"tags": { "type": "keyword" }
}
}
}
5.2 批量处理优化
推荐配置:
- Kafka消费者:
max.poll.records=500 - ES Bulk API:每批1000条,间隔1秒
java复制BulkRequest bulkRequest = new BulkRequest();
for (Message msg : messages) {
bulkRequest.add(new IndexRequest("index")
.id(msg.id)
.source(msg.data));
}
if (!bulkRequest.requests().isEmpty()) {
client.bulk(bulkRequest);
}
6. 最终一致性验证方案
开发数据比对工具:
sql复制-- MySQL数据提取
SELECT id, name, price FROM products;
# ES数据提取
GET /products/_search {
"query": { "match_all": {} }
}
比对脚本示例:
python复制def compare(mysql_data, es_data):
mismatches = []
for mid, mrow in mysql_data.items():
erow = es_data.get(mid)
if not erow or any(mrow[k] != erow[k] for k in mrow):
mismatches.append(mid)
return mismatches
我在实际项目中总结的最佳实践是:每天凌晨低峰期全量比对,发现不一致立即触发补偿同步,同时报警通知运维。这套方案将数据不一致时间窗口控制在5分钟以内,可靠性达到99.99%。