当前位置: 首页 > news >正文

如何保证缓存和数据库的双写一致性

程序员面试资料大全|各种技术书籍等资料-1000G
IDEA开发工具- FREE

一、双写一致性问题本质

在分布式系统中,缓存与数据库双写一致性指当数据被修改时,如何确保缓存(如Redis)和数据库(如MySQL)中的数据保持同步。核心挑战在于处理并发操作系统故障场景下的数据一致性问题。

典型不一致场景

ClientA Cache DB ClientB 更新数据X=1 成功 删除缓存X 读X(缓存未命中) 读X(旧值X=0) 写入缓存X=0 缓存中存储了旧值 ClientA Cache DB ClientB

二、主流解决方案对比

方案适用场景优点缺点一致性强度
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(穿透写入)

最佳实践:强一致性要求场景

Client Cache DB 写请求 同步写入数据 写入结果 操作结果 Client Cache DB

特点:

  • 缓存层作为数据库代理
  • 所有写操作同步更新缓存和数据库
  • 读操作只访问缓存

方案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)

最佳实践:高一致性要求场景

Client Cache DB 第一次删除缓存 更新数据库 第二次删除缓存(延迟) Client Cache DB

实现代码:

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的数据同步

Binlog
MySQL
消息队列
缓存更新服务
Redis

实现组件:

  1. Canal监听MySQL Binlog
  2. Kafka/RocketMQ作为消息队列
  3. 消费者服务更新缓存

优点:

  • 完全解耦
  • 保证最终一致性
  • 支持重试机制

五、异常场景处理方案

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+消费者集群

七、性能优化技巧

  1. 批量处理:合并多个缓存操作

    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]));
    }
    
  2. 热点数据特殊处理

    // 使用互斥锁防止缓存击穿
    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;
    }
    
  3. 多级缓存策略

    客户端
    CDN
    边缘缓存
    Redis集群
    本地缓存
    数据库

八、监控与度量指标

  1. 关键监控项

    • 缓存命中率(Hit Ratio)
    • 缓存更新延迟(Update Latency)
    • 不一致事件计数
    • 重试队列长度
  2. 告警规则

    # 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

在这里插入图片描述

http://www.dtcms.com/a/265244.html

相关文章:

  • Python训练营Day1
  • 软件测试用例大全
  • 基于大模型的智能体中由自主性引发的安全风险综述
  • CLIP heat map generation
  • 【深度学习:进阶篇】--4.2.词嵌入和NLP
  • MinHook 如何对 .NET 母体 CoreCLR 进行拦截
  • 在米联客4EV上部署tinyriscv
  • 【深度学习新浪潮】国内零样本抗体设计的科研进展如何?
  • docker-compose.yml 文件详解——AI教你学Docker
  • 从一个开发的角度切入mysql索引,查询优化
  • C Primer Plus 第6版 编程练习——第6章(上)
  • 设计模式-享元模式
  • JAVA内存区域划分
  • WEB测试总结
  • ubuntu 安装neo4j
  • 七、分工说明
  • 南方大暴雨及洪水数据分析与可视化
  • 爬虫从入门到精通(22) |TLS指纹
  • 【RHCSA-Linux考试题目笔记(自用)】servera的题目
  • 【C++】--入门
  • HTTPS详解:原理 + 加解密过程 + 面试问答
  • SpringBoot:整合quartz实现定时任务-基础篇
  • 从零用java实现 小红书 springboot vue uniapp (12)实现分类筛选与视频笔记功能
  • uniapp 滚动tab
  • autoware calar 联合运行,自动驾驶虚拟仿真器
  • ESP32S3开发:实现WiFi扫描与连接功能
  • 插值与拟合(3):B样条曲线
  • MySQL 用户管理与权限控制
  • 进阶向:Django框架深度解析各核心组件的作用与协作
  • Spring生态在Java开发