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

如何保证Redis和Mysql数据缓存一致性?

1.读数据流程:

1. 先读缓存
2. 缓存命中 → 直接返回
3. 缓存未命中 → 读数据库 → 写入缓存 → 返回数据

2.写数据流程:

1. 更新数据库
2. 删除缓存(而不是更新缓存)

3.完整代码示例

3.1.用户服务实现

@Service
@Slf4j
public class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RedisCache redisCache;// 缓存key前缀private static final String USER_CACHE_KEY = "user:";/*** 根据ID获取用户(读操作 - 旁路缓存)*/public User getUserById(Long userId) {String cacheKey = USER_CACHE_KEY + userId;// 1. 先从缓存读取User user = redisCache.get(cacheKey);if (user != null) {log.info("缓存命中,用户ID: {}", userId);return user;}// 2. 缓存未命中,查询数据库log.info("缓存未命中,查询数据库,用户ID: {}", userId);user = userMapper.selectById(userId);if (user == null) {return null;}// 3. 将数据写入缓存,设置30分钟过期redisCache.set(cacheKey, user, 30 * 60);log.info("数据写入缓存,用户ID: {}", userId);return user;}/*** 更新用户(写操作 - 先更新数据库,再删除缓存)*/@Transactionalpublic boolean updateUser(User user) {try {// 1. 更新数据库int result = userMapper.updateById(user);if (result <= 0) {return false;}// 2. 删除缓存String cacheKey = USER_CACHE_KEY + user.getUserId();redisCache.delete(cacheKey);log.info("更新用户成功,删除缓存,用户ID: {}", user.getUserId());return true;} catch (Exception e) {log.error("更新用户失败,用户ID: {}", user.getUserId(), e);throw new RuntimeException("更新用户失败", e);}}/*** 新增用户*/@Transactionalpublic boolean addUser(User user) {try {// 1. 插入数据库int result = userMapper.insert(user);if (result <= 0) {return false;}// 2. 新增用户不需要立即缓存,等第一次读取时自然缓存// 但如果需要立即使用,可以在这里设置缓存// String cacheKey = USER_CACHE_KEY + user.getUserId();// redisCache.set(cacheKey, user, 30 * 60);log.info("新增用户成功,用户ID: {}", user.getUserId());return true;} catch (Exception e) {log.error("新增用户失败", e);throw new RuntimeException("新增用户失败", e);}}/*** 删除用户*/@Transactionalpublic boolean deleteUser(Long userId) {try {// 1. 删除数据库(逻辑删除)User user = new User();user.setUserId(userId);user.setDelFlag("1"); // 逻辑删除标志int result = userMapper.updateById(user);if (result <= 0) {return false;}// 2. 删除缓存String cacheKey = USER_CACHE_KEY + userId;redisCache.delete(cacheKey);log.info("删除用户成功,删除缓存,用户ID: {}", userId);return true;} catch (Exception e) {log.error("删除用户失败,用户ID: {}", userId, e);throw new RuntimeException("删除用户失败", e);}}
}

3.2 控制器层

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {@Autowiredprivate UserService userService;/*** 获取用户信息*/@GetMapping("/{userId}")public AjaxResult getUser(@PathVariable Long userId) {try {User user = userService.getUserById(userId);if (user == null) {return AjaxResult.error("用户不存在");}return AjaxResult.success(user);} catch (Exception e) {log.error("获取用户信息失败,用户ID: {}", userId, e);return AjaxResult.error("系统异常");}}/*** 更新用户信息*/@PutMappingpublic AjaxResult updateUser(@RequestBody User user) {try {boolean result = userService.updateUser(user);return result ? AjaxResult.success() : AjaxResult.error("更新失败");} catch (Exception e) {log.error("更新用户信息失败,用户ID: {}", user.getUserId(), e);return AjaxResult.error("更新失败");}}/*** 新增用户*/@PostMappingpublic AjaxResult addUser(@RequestBody User user) {try {boolean result = userService.addUser(user);return result ? AjaxResult.success() : AjaxResult.error("新增失败");} catch (Exception e) {log.error("新增用户失败", e);return AjaxResult.error("新增失败");}}/*** 删除用户*/@DeleteMapping("/{userId}")public AjaxResult deleteUser(@PathVariable Long userId) {try {boolean result = userService.deleteUser(userId);return result ? AjaxResult.success() : AjaxResult.error("删除失败");} catch (Exception e) {log.error("删除用户失败,用户ID: {}", userId, e);return AjaxResult.error("删除失败");}}
}

4.使用建议

1. 缓存key设计

// 使用统一的命名规范
private static final String USER_CACHE_KEY = "user:";
private static final String PRODUCT_CACHE_KEY = "product:";
private static final String ORDER_CACHE_KEY = "order:";

2. 过期时间策略

// 根据业务特点设置不同的过期时间
redisCache.set(key, value, 30 * 60);        // 用户数据:30分钟
redisCache.set(key, value, 5 * 60);         // 商品数据:5分钟  
redisCache.set(key, value, 24 * 60 * 60);   // 配置数据:24小时

3. 监控和日志

// 添加详细的日志,便于排查问题
log.info("缓存命中,key: {}", key);
log.info("缓存未命中,查询数据库,key: {}", key);
log.info("删除缓存,key: {}", key);

5.CAP理论的基本概念

CAP理论主要用于描述分布式系统中的三大特性:

  1. 一致性(Consistency):所有节点在同一时刻看到的数据是一致的。即每次读操作都能返回最新的写入结果,确保数据的正确性。

  2. 可用性(Availability):系统能够保证每个请求都会收到响应,无论是成功的响应还是失败的响应。即使某些节点出现故障,系统仍然能够继续提供服务。

  3. 分区容忍性(Partition Tolerance):系统能够容忍网络分区,即节点之间的通信中断,并且在出现分区时仍然能够继续运作。

6.为什么选择"更新DB + 删除缓存"而不是"更新DB + 更新缓存"?

方案对比:

方案优点缺点
更新DB + 删除缓存简单、避免并发写问题、减少不必要的缓存更新有极小的不一致窗口期
更新DB + 更新缓存数据一致性更好并发写时可能脏数据、浪费资源更新不常读的数据

并发问题分析:

场景:两个并发写操作

  • 线程A更新用户信息 → 更新DB → 更新缓存

  • 线程B更新用户信息 → 更新DB → 更新缓存

可能出现:

DB中:用户B的数据
缓存中:用户A的数据(因为A的更新晚于B)

而使用删除缓存策略,可以避免这种并发写导致的脏数据问题。

针对删除缓存异常的情况,可以使用 2 个方案避免:

1.消息队列方案:将第二个操作(删除缓存)要操作的数据加入到消息队列,由消费者来操作数据。

2.订阅 MySQL binlog,再操作缓存:

先更新数据库,再删缓存」的策略的第一步是更新数据库,那么更新数据库成功,就会产生一条变更日志,记录在 binlog 里。于是我们就可以通过订阅 binlog 日志,拿到具体要操作的数据,然后再执行缓存删除。

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

相关文章:

  • 八股-Mysql 基础篇(1)
  • 建设公司网站需要准备什么科目苏州建网站的公司
  • Git 某个分支恢复到某个特定的 commit 状态
  • 【Prompt学习技能树地图】生成知识提示技术的深度解析与应用
  • 家用电脑怎么做网站服务器创意设计之都
  • 使用 Spire.XLS for Python 将 Excel 转换为 PDF
  • vue3封装el-dialog
  • 河南郑州网站顾问大连建设工程信息网官网入口
  • 记录一次K8S跨命名空间访问 xxx.xxx.svc.cluster.local 类似内部服务不通的问题
  • Go Slice 实现原理深度解析:从底层机制到工程实践
  • 网站开发需要什么基础只是怎么下载文件
  • Centos 7安装Apache Drill
  • AI与电力的深度绑定:算力与能源分配的趋势分析
  • 【轨物洞见】 新型能源体系建设:十五五规划建议推进能源转型
  • 第四代核反应堆:未来能源的“安全高效革命”
  • HarmonyOS实战项目:打造沉浸式AR导航应用(空间计算与虚实融合)
  • 建行信用卡网站官网入口2022好用值得推荐的搜索引擎
  • 多模态化学信息重建系统CIRS-图像处理单元复现源码:从化学结构图像到图形化基元的转化
  • 评估工程正成为下一轮 Agent 演进的重点
  • 易营宝智能建站做网站类型
  • CTFHub Web进阶-PHP:Bypass_disable_function通关5之GC UAF
  • Jmeter+Maven+jenkins+eclipse 搭建自动化测试平台
  • Cursor 2.0:让 AI 编码更快、更协同的全新平台
  • 兰州网站建设推荐q479185700顶你本地视频做成链接网址
  • Flutter for HarmonyOS 开发指南(一):环境搭建与项目创建
  • Flutter 如何使用fvm进行多项目sdk管理
  • 【Git】-- Rebase 减少 Commit 次数指南
  • 北京网站设计培训学校cn 域名网站
  • 广州广州网站建设公司阿里云网站模板
  • SpringBoot教程(三十二)| SpringBoot集成Sentinel