当前位置: 首页> 科技> 能源 > 长春信息网招聘_湖南省郴州市疫情最新情况_目前最牛的二级分销模式_2023年5月疫情爆发

长春信息网招聘_湖南省郴州市疫情最新情况_目前最牛的二级分销模式_2023年5月疫情爆发

时间:2025/7/12 12:37:30来源:https://blog.csdn.net/liuruiaaa/article/details/145574748 浏览次数:0次
长春信息网招聘_湖南省郴州市疫情最新情况_目前最牛的二级分销模式_2023年5月疫情爆发

逻辑分析:数据库读写分离+缓存一致性

数据库读写分离 的架构下,读请求从 从库 查询,写请求更新 主库,然后再通过 主从同步 将数据同步到 从库。但同步有延迟,可能导致查询到旧数据,并错误地写入缓存,造成 缓存脏数据

问题复现

  1. 请求A(写操作)

    • 删除缓存
    • 更新数据库(主库)
    • 主从同步有延迟
  2. 请求B(读操作)

    • 查询缓存,发现数据不存在
    • 从从库读取数据(由于主从同步延迟,数据是旧的)
    • 将旧数据写回缓存
  3. 主从同步完成,但缓存仍然是旧数据,导致后续请求仍然读取错误数据。


解决方案:延时双删策略

核心思路

  1. 第一次删除缓存(避免查询到旧值)
  2. 写入数据库(主库)
  3. 等待一段时间(大于主从同步的延迟)
  4. 第二次删除缓存(确保从库同步完成后缓存不存旧数据)

Demo

1. 读操作(查询)

public Object read(String key) {// 1. 先查询缓存Object data = redisUtils.get(key);if (data != null) {return data; // 缓存命中,直接返回}// 2. 缓存未命中,从数据库(从库)查询data = db.readFromSlave(key);// 3. 由于主从同步延迟,我们需要对数据进行额外判断if (data != null) {// 4. 异步任务或延迟写入缓存,避免脏数据final Object finalData = data;new Thread(() -> {try {Thread.sleep(200); // 等待主从同步完成(时间根据业务调整)redisUtils.set(key, finalData);} catch (InterruptedException e) {e.printStackTrace();}}).start();}return data;
}

2. 写操作(更新)

public void write(String key, Object data) {// 1. 删除缓存(防止脏数据)redisUtils.del(key);// 2. 更新数据库(主库)db.updateToMaster(data);// 3. 延迟删除缓存(确保主从同步完成)new Thread(() -> {try {Thread.sleep(500); // 这里的时间要大于主从同步时间redisUtils.del(key);} catch (InterruptedException e) {e.printStackTrace();}}).start();
}
  • 第一次删除缓存(防止旧数据被读取)
  • 写入数据库
  • 等待主从同步完成
  • 第二次删除缓存(防止旧数据回写缓存)

这样可以有效防止 数据库读写分离 造成的 缓存脏数据,保证 缓存与数据库的一致性

问题是这个“Thread.sleep(500);”还需要改进。

如何控制 Thread.sleep(500) 时间?是否可以加分布式锁?

直接使用 Thread.sleep(500) 来等待主从同步完成 并不精准,因为:

  1. 主从同步时间不固定
    • 数据库负载、网络带宽、事务量 等影响,实际同步时间可能大于 500ms 或更短。
  2. 固定等待时间可能导致性能问题
    • 过长:影响写入效率。
    • 过短:缓存删除得太早,仍可能出现缓存脏数据问题。

所以,理想的方法是 动态调整缓存删除的时机,保证数据正确性的同时减少性能损耗。


解决方案

可以用 分布式锁 + 异步回调机制 来更精准地控制缓存删除的时机,而不是简单的 Thread.sleep()

方案 1:使用分布式锁,确保主从同步完成后删除缓存

核心思路

  • 更新数据库时获取分布式锁,保证只有一个线程在操作缓存。
  • 异步任务轮询主从同步状态,当数据同步完成后删除缓存,而不是等待固定时间。

实现示例(基于 Redis 分布式锁 + 轮询检测主从同步状态)

public void write(String key, Object data) {// 1. 删除缓存(防止脏数据)redisUtils.del(key);// 2. 更新数据库(主库)db.updateToMaster(data);// 3. 使用分布式锁防止并发修改缓存String lockKey = "lock:" + key;String requestId = UUID.randomUUID().toString();if (redisUtils.tryLock(lockKey, requestId, 10, TimeUnit.SECONDS)) { // 异步任务轮询主从同步状态new Thread(() -> {try {int retryCount = 0;while (retryCount < 5) { // 最多尝试 5 次Thread.sleep(100); // 每次间隔 100ms// 查询从库是否同步完成Object newData = db.readFromSlave(key);if (newData != null && newData.equals(data)) {redisUtils.del(key); // 4. 确保同步完成后删除缓存break;}retryCount++;}} catch (InterruptedException e) {e.printStackTrace();} finally {redisUtils.releaseLock(lockKey, requestId); // 释放锁}}).start();}
}

方案 2:让数据库提供主从同步完成的事件通知

有些数据库(如 MySQL 8.0+)支持 binlog 事件通知,可以监听主从同步完成的状态,然后再删除缓存。
不过,这种方式需要 额外的消息队列(如 Kafka、RabbitMQ)监听 MySQL Binlog

步骤
  1. 监听 MySQL Binlog(主从同步完成时触发事件)
  2. 收到同步完成事件后删除缓存
实现思路
// 监听 MySQL Binlog 日志,当主从同步完成时删除缓存
public void onReplicationComplete(String key) {redisUtils.del(key);
}

缺点:需要额外搭建 Binlog 监听 系统,复杂度较高。


总结

方案方式优点缺点
固定等待时间 (Thread.sleep())简单延迟实现简单主从同步时间不固定,可能太长或太短
分布式锁 + 轮询查询先删除缓存 -> 轮询从库状态,等同步完成再删缓存精确控制缓存删除时间,无需固定 sleep()多次查询数据库,有一定性能消耗
监听 MySQL BinlogMySQL 主从同步完成时触发删除缓存事件驱动,精准控制需要搭建 Binlog 监听系统,较复杂

推荐

  • 如果你的系统支持 Binlog 监听,可以使用 方案 3(监听数据库同步事件),精准删除缓存。
  • 如果不支持 Binlog,建议用 方案 2(分布式锁 + 轮询数据库),在主从同步完成后删除缓存,避免使用 Thread.sleep() 固定延迟。

关键字:长春信息网招聘_湖南省郴州市疫情最新情况_目前最牛的二级分销模式_2023年5月疫情爆发

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: