Java17 --- redis7缓存双写一致性

一、缓存双写一致性

  1. 如果redis中有数据:需要和数据库中的值相同。
  2. 如果redis中没有数据:数据库中的值要是最新值,且准备回写redis。
  3. 只读缓存。
  4. 读写缓存:①、同步直写策略:写数据库后也同步写redis缓存,缓存和数据库中的数据一致,对于读写缓存来说,要想保证缓存和数据库中的数据一致,就要采用同步直写策略。②、异步缓写策略:正常业务运行中,mysql数据变动了,但是可以在业务上容许出现一定时间后才作用于redis,如仓库、物流等功能。异常情况出现了,不得不将失败的动作重新修补,有可能需要借助kafka或者rabbitMQ等消息中间件,实现重试重写。
  5. 双检加锁策略:多个线程同时去查询数据库的这条数据,那么就可以第一个查询数据的请求上使用一个互斥锁来锁住它。其他线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。

1.1、数据库和缓存一致性的更新策略

目的:给缓存设置过期时间,定期清理缓存并回写,是保证最终一致性的解决方案。

可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。就是如果数据库写入成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存,达到一致性,要以mysql的数据库写入库为准。

1.1.1、在停机的情况下

给出公告,服务升级,单线程,这样重量级的数据操作最好不要多线程。

1.1.2、先更新数据库,再更新缓存

1、情况1:①、先更新mysql的某商品的库存,当前商品的库存是100,更新为99。②、先更新mysql修改为99成功,然后更新redis。③、出现异常,更新redis失败了,导致MySQL里面的库存是99而redis里面还是100。所以会导致数据库里的数据和缓存redis里面数据不一致,读到redis脏数据。

2、情况2:在多线程环境下,A,B两个线程有快有慢。①、A更新mysql为100。②、B更新mysql为90。③、B先更新redis为90。④、A再更新redis为100。所以导致redis与mysql更新的数据不一致。

1.1.3、先更新缓存,再更新数据库

不推荐:业务上一般把mysql作为底单数据库,保证最后解释。

1.1.4、先删除缓存,再更新数据库

1、请求A进行写操作,删除redis缓存后,工作正在进行中,更新mysql……A还没有彻底更新完mysql,还没commit。

2、请求B开工查询,查询redis发现缓存不存在(被A从redis中删除了)。

3、请求B继续,去数据库查询得到了mysql中的旧值(A还没有更新完)。

4、请求B将旧值写回redis缓存。

5、请求A将新值写入mysql数据库。

这样依然会导致数据不一致的情况发生。

解决方法:采用延时双删策略,A线程删除redis缓存,然后sleep一段时间,这期间就是为了让B线程先从数据库读取数据,再把缺失的数据写入缓存,然后线程A再进行删除。所以,线程A sleep的时间,

### Redis 缓存一致性策略 为了确保在使用 Redis 作为缓存时实现一致性,即保持 Redis 和主数据库之间的数据同步,可以采用多种技术和最佳实践。这些技术旨在解决常见的不一致问题并提高系统的可靠性和性能。 #### 使用事务机制 通过引入分布式事务来管理 Redis 和主数据库的操作顺序,可以在一定程度上减少因并发操作引起的不一致现象。虽然 Redis 自身支持简单的多命令执行模式(MULTI/EXEC),但对于跨多个存储系统的场景,则需借助外部工具或框架完成更复杂的协调工作[^1]。 #### 实现最终一致性模型 考虑到强一致性的成本较高,在某些应用场景下可以选择较为宽松的最终一致性方案。具体做法是在更新源端之后异步通知目标端进行相应的变更处理;或者定期扫描对比两端的数据差异,并自动修复发现的不同之处。这种方式能够有效降低延迟时间的同时维持较高的吞吐量水平[^2]。 #### 应用消息队列中间件 利用 Kafka、RabbitMQ 等消息传递服务构建事件驱动架构也是一种常见手段。每当发生影响到共享资源状态变化的动作时就发送一条记录给订阅者们知晓。接收方监听特定主题的消息流后作出响应动作,比如刷新本地副本中的陈旧条目或是触发后台任务重新加载最新版本的信息集合[^3]。 ```java // Java伪代码示例:基于Spring Data Redis和JPA实现向同步逻辑 @Autowired private JpaRepo jpaRepository; @Autowired private StringRedisTemplate stringRedisTemplate; @Transactional(propagation = Propagation.REQUIRED) public void saveEntity(Entity entity){ // Save into main DB first. Entity saved = this.jpaRepository.save(entity); try { // Then update the cache with new value. ValueOperations<String, String> ops = template.opsForValue(); ops.set("entity:" + saved.getId(), objectMapper.writeValueAsString(saved)); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鸭鸭老板

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值