redis缓存和数据库数据保持一致

文章目录

一般情况下,redis是作为缓存,当数据库中有数据修改时,要做到缓存和修改后的数据保持一致

一般我们在更新数据库数据时,需要同步redis中缓存的数据,所以存在不同的方案,这里我简单分为2个大类进行总结,每个大类下又有不同的方案

代码自己解决

只通过代码,不需要其他第三方组件介入

先更新数据库后删除缓存

  1. 更新数据库
  2. 删除缓存

存在问题:

  1. 步骤1成功,步骤2失败,那后续读取的一直都是旧缓存
  2. 即使步骤1和步骤2都成功,但步骤1和步骤2执行过程中,如果有并发请求:线程1步骤1------>线程2读请求------>线程1步骤2,此时线程2读取的也是旧数据

先删除缓存后更新数据库

  1. 删除缓存
  2. 更新数据库

存在问题:

请求1清除缓存------>请求2读请求,没有缓存,读DB并写入redis------>请求1更新数据库

当请求1执行清除缓存后,还未进行update操作,此时请求2进行查询到了旧数据并写入了redis,后续读取一直都是旧数据

注意:无论是先更新数据库还是后更新数据库,有的会把更新缓存(不是删除)也作为一种解决方案,但生产中真正使用时,都是删除缓存

为什么是删除而不是更新?因为更新缓存成本更高,而且可能产生并发问题。删除缓存后,下次读取时会自动回填

延迟双删

  1. 删除缓存
  2. 更新数据库
  3. 延迟N秒,再删除缓存(延迟的时间>写入redis的时间)

延迟的原因:保证请求2的旧数据已经完全写到redis中了,这样就可以把旧数据完全删除;如果延迟时间小于写入redis的时间,可能请求2的旧数据还没有写入redis,还是会造成数据不一致

java 复制代码
// 伪代码如下
public void updateData(String key,Object data){
	// 第一次删除缓存
	redisTemplate.delete(key);

	// 更新数据库
	database.update(data);

	// 第二次删除缓存(延迟,保证并发读请求把旧数据完全写入redis)
	Thread.sleep(500);
	redisTemplate.delete(key);
}

优点:实现简单,能有效解决大部分并发问题

缺点:

  • 延迟时间内仍可能存在短暂不一致
  • 第二次删除缓存要设置一定的延迟时间(Thread.sleep),所有的修改数据都要延迟,牺牲系统性能

读写锁/分布式锁

在极端高并发场景下,可以使用分布式锁保证同一时刻只有一个线程在操作某个key的缓存(操作缓存时,写操作加锁,读操作不加锁)

优点:保证强一致性,防止缓存击穿

缺点:引入锁降低了并发性能,适用于并发极高且一致性要求严格的场景

java 复制代码
public Data readDataWithLock(String key) {
   // 先查缓存
   Data data = redisTemplate.get(key);
   if (data != null) return data;
   
   // 缓存未命中,加锁后查数据库
   String lockKey = "lock:" + key;
   RLock lock = redissonClient.getLock(lockKey);
   try {
       lock.lock(5, TimeUnit.SECONDS);
       // 双重检查,防止其他线程已经回填了缓存
       data = redisTemplate.get(key);
       if (data != null) return data;
       
       // 查数据库并回填缓存
       data = database.query(key);
       // 因为写回redis的代码在锁中,所以操作这个key的只有一个线程
       redisTemplate.set(key, data);
       return data;
   } finally {
       lock.unlock();
   }
}

接入其他组件

消息队列MQ

将缓存删除操作封装成消息发送到MQ,由消费者异步执行删除。即使删除失败,也可以通过MQ的重试机制保证最终删除成功。

优点:解耦、可靠,通过MQ的重试机制保证最终一致性

缺点:引入MQ增加了系统复杂度,且数据存在一定延迟(不一致)

订阅Binlog同步(Canal方案)

阿里巴巴开源的Canal组件可以监听MySQL的binlog变更,然后将变更推送给Redis进行同步

MySQL binlog → Canal → MQ → 消费者 → 更新Redis

优点:

  1. 与业务代码完全解耦,不需要在业务代码中写缓存更新逻辑
  2. 不入侵原有业务代码
  3. 可靠性高,binlog是MySQL原生机制

缺点:

  1. 引入Canal和MQ增加了系统复杂度
  2. 存在一定的同步延迟(秒级)

总结:没有完美的方案,只能根据业务场景选择

只要享受了缓存的高速度,就注定无法保证数据实时一致性,只能尽可能降低数据不一致的概率,并保证最终一致性

相关推荐
时空无限3 小时前
vllm 大模型启动缓存相关环境变量 export
linux·缓存·vllm
宇宙第一小趴菜3 小时前
三、Oracle 核心原理
数据库·oracle
码出钞能力4 小时前
apache-shardingsphere-5.5.3自定义分片算法
数据库
兜有米啦4 小时前
数据库第二次作业
数据库
nVisual5 小时前
机柜PDU安装位置与空间建模方案
大数据·网络·数据库·信息可视化·数据中心基础设施管理
weixin_6685 小时前
Cursor-superpowers插件用法
数据库·人工智能
流星白龙5 小时前
【Redis】8.List列表
redis
NWU_白杨5 小时前
三种常用的数据存储技术
数据库·redis·mysql·sqlite
我叫黑大帅5 小时前
我为什么单一消费者的场景下,要用 Redis List 当消息队列?
redis·后端·面试