如何保证缓存和数据库的双写一致性
程序员面试资料大全|各种技术书籍等资料-1000G
IDEA开发工具- FREE
一、双写一致性问题本质
在分布式系统中,缓存与数据库双写一致性指当数据被修改时,如何确保缓存(如Redis)和数据库(如MySQL)中的数据保持同步。核心挑战在于处理并发操作和系统故障场景下的数据一致性问题。
典型不一致场景
二、主流解决方案对比
方案 | 适用场景 | 优点 | 缺点 | 一致性强度 |
---|---|---|---|---|
Cache-Aside | 读多写少 | 简单易实现 | 存在不一致时间窗口 | 最终一致 |
Write-Through | 写密集型 | 强一致性保证 | 性能损耗大 | 强一致 |
Write-Behind | 高吞吐场景 | 高性能 | 数据丢失风险 | 最终一致 |
双删策略 | 高一致性要求 | 减少不一致窗口 | 实现复杂 | 强一致 |
三、核心解决方案详解
方案1:Cache-Aside(旁路缓存)
最佳实践:读多写少场景
关键实现代码:
public void updateData(Data data) {// 1. 更新数据库dataDao.update(data);// 2. 删除缓存redis.del(data.getId());
}public Data getData(String id) {// 1. 从缓存获取Data data = redis.get(id);if (data != null) {return data;}// 2. 从数据库读取data = dataDao.get(id);// 3. 写入缓存(设置过期时间)redis.setex(id, 300, data);return data;
}
方案2:Write-Through(穿透写入)
最佳实践:强一致性要求场景
特点:
- 缓存层作为数据库代理
- 所有写操作同步更新缓存和数据库
- 读操作只访问缓存
方案3:Write-Behind(异步回写)
最佳实践:高吞吐场景
实现代码示例:
// 使用内存队列实现异步更新
private BlockingQueue<Data> writeQueue = new LinkedBlockingQueue<>();public void updateData(Data data) {// 1. 更新缓存redis.set(data.getId(), data);// 2. 加入异步队列writeQueue.offer(data);
}// 单独的消费者线程
class DbWriter implements Runnable {public void run() {while (true) {Data data = writeQueue.take();dataDao.update(data); // 批量更新优化}}
}
方案4:双删策略(Double Delete)
最佳实践:高一致性要求场景
实现代码:
public void updateDataWithDoubleDelete(Data data) {// 1. 首次删除缓存redis.del(data.getId());// 2. 更新数据库dataDao.update(data);// 3. 延迟二次删除executor.schedule(() -> {redis.del(data.getId());}, 500, TimeUnit.MILLISECONDS); // 500ms延迟
}
四、高级一致性保障方案
方案1:分布式事务(强一致)
实现技术:
- 2PC(两阶段提交)
- TCC(Try-Confirm-Cancel)
- Saga事务模式
方案2:基于Binlog的数据同步
实现组件:
- Canal监听MySQL Binlog
- Kafka/RocketMQ作为消息队列
- 消费者服务更新缓存
优点:
- 完全解耦
- 保证最终一致性
- 支持重试机制
五、异常场景处理方案
1. 缓存更新失败
2. 数据库更新失败
- 事务回滚
- 补偿机制恢复缓存
public void updateDataWithCompensation(Data data) {try {// 1. 开启事务transaction.begin();// 2. 更新数据库dataDao.update(data);// 3. 删除缓存redis.del(data.getId());// 4. 提交事务transaction.commit();} catch (Exception e) {// 5. 事务回滚transaction.rollback();// 6. 恢复缓存Data oldData = dataDao.get(data.getId());redis.set(data.getId(), oldData);}
}
六、最佳实践选择指南
场景特征 | 推荐方案 | 配置建议 |
---|---|---|
读多写少,容忍短暂不一致 | Cache-Aside | 缓存过期时间 5-30分钟 |
写密集型,强一致性要求 | Write-Through | 配合本地缓存减少DB压力 |
超高吞吐,可接受秒级延迟 | Write-Behind | 批量大小100-500条,刷新间隔1s |
金融交易类系统 | 分布式事务 | TCC模式+异步对账 |
大型电商平台 | Binlog同步 | Canal+Kafka+消费者集群 |
七、性能优化技巧
-
批量处理:合并多个缓存操作
public void batchUpdate(List<Data> dataList) {// 批量更新数据库dataDao.batchUpdate(dataList);// 批量删除缓存List<String> keys = dataList.stream().map(Data::getId).collect(Collectors.toList());redis.del(keys.toArray(new String[0])); }
-
热点数据特殊处理
// 使用互斥锁防止缓存击穿 public Data getHotData(String id) {Data data = redis.get(id);if (data == null) {if (redis.setnx("lock:" + id, "1")) {redis.expire("lock:" + id, 10); // 设置锁超时data = dataDao.get(id);redis.set(id, data);redis.del("lock:" + id);} else {// 等待重试Thread.sleep(50);return getHotData(id);}}return data; }
-
多级缓存策略
八、监控与度量指标
-
关键监控项:
- 缓存命中率(Hit Ratio)
- 缓存更新延迟(Update Latency)
- 不一致事件计数
- 重试队列长度
-
告警规则:
# Prometheus告警规则示例 - alert: HighCacheInconsistencyRateexpr: rate(cache_inconsistency_count[5m]) > 0.5for: 10mlabels:severity: criticalannotations:summary: "缓存不一致率过高"- alert: CacheUpdateTimeoutexpr: cache_update_latency_seconds > 1for: 5mlabels:severity: warningannotations:summary: "缓存更新延迟超过阈值"
程序员面试资料大全|各种技术书籍等资料-1000G
IDEA开发工具- FREE