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. 存在一定的同步延迟(秒级)

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

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

相关推荐
这个DBA有点耶1 天前
MVCC深入:Read View、版本链与快照读——InnoDB并发控制的内核
数据库·mysql·架构
DBA_G1 天前
从地面到云霄:GBase数据库在民航三大场景的落地实践
数据库
自由能燃气设备1 天前
商用全预混低氮冷凝锅炉免费方案vs付费方案对比+选型避坑指南
大数据·数据库·人工智能
科创致远1 天前
科创致远 ESOP 系统核心效能与实战价值展示
大数据·数据库·人工智能·精益工程
IT大白鼠1 天前
Redis 系列 · 第 01 篇——认知入门:Redis 是什么
redis·nosql
彧azz1 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
2601_962218611 天前
万象生鲜系统称重自动多退少补算法解决生鲜非标品痛点
大数据·数据库·人工智能·python·算法
全栈弄潮儿²⁰²⁴1 天前
AI Agent 开发实战(30):限流、缓存与成本控制
人工智能·gpt·缓存·agent·限流·agi·成本控制
张洛闻Eren1 天前
k8s云原生【第十课】:水平 Pod 自动扩缩容
运维·数据库·云原生·kubernetes·github
于平安1 天前
MySQL-触发器
数据库·mysql