分布式部署下Redis缓存与数据库不一致
文章目录
前言
在我们大部分使用redis进行缓存场景时一般流程是这样的
- 用户请求redis 查询redis是否存在数据
- 存在数据时直接返回redis 中的数据
- 不存在数据时读取mysql中的数据并且写入一个redis的过期时间
总结一下,上述过程虽然有可能在读取mysql 数据库去更新redis的时候有其他的用户对数据进行了重新写入,会有一段时间的脏数据,但是设置了过期时间最终还是会以数据库的数据为准的,实现最终一致性的方案
一、缓存不一致场景
、当如果我们不想依托于过期时间实现的最终一致性方案的话,就有了下面几种场景
1. 先更新数据库,进行redis缓存的更新(mysqlUpdate – redisUpdate)
会造成大量的脏读数据,场景如下
1.A 请求先更新了数据但是在去执行redis缓存更新的时候可能由于某台服务器网络原因导致不能立即对redis进行更新,此时B请求进入了另外一台服务器,B更新了数据库最新的数据并且立即对redis执行了更新操作,此时A请求在B请求之后对Redis进行缓存更新造成了redis里面的数据是一个脏数据。
2.开销问题,当如果我们是一个写大于读的一个场景的话,用户并没有去读取数据,就去频繁更新缓存,无疑是一笔不小的开销。如果每次写入缓存还需要涉及到一些计算的话,这个开销就会更大
2. 先删除缓存在,在更新数据库(redisDelete - mysqlUpdate )
脏读一般会发生在用户读写并发时场景如下
1.当用户A 需要请求更新数据库数据时,首先先删除缓存,删除完缓存后此时,B请求进入了,发现缓存不存在,会去数据库读取数据放入到缓存中,此时A请求还没开始跟新数据库,等到A请求执行完毕后,redis缓存中的数据并不是最新的数据,而且这种情况下面如果你redis并没有使用过期时间的话,那么缓存中一直都将会是脏数据
解决方案-延迟双删除策略
实现原理
Delete Redis -> mysql Update -> Thread.sleep(500)->Delete Redis
4. 这种情况下面主要是保证了在第一次redis缓存删除到 mysql 事务提交的这段时间内的脏数据进行一个删除,当用户重新请求时,就会去数据库拉取最新的数据了
延迟双删除带来的问题
1.使用了Thread.sleep 降低了系统的一个吞吐性,解决方案 可以使用异步的一个调用方式,线程池是一个很好的选择
3. 数据库使用了主从同步的情况.可以在Thread.sleep(500+预计主从同步需要耗时的时间)
5. 第二次删除缓存失败
由于一系列原因二次缓存可能存在缓存删除失败,可以使用各种重试机制,消息队列,自旋,spring 的重试机制都可以,大家可以发挥自己的想象找到自己最合适的方法。
3. 先更新数据库,进行redis缓存的删除 (mysqlUpdate – redisDelete)
这种方式我我觉得有点像乐观的方式,因为他的原理是认为在读写并发的情况下面,读的效率肯定是优于写的效率的,所以认为对于redis delete 操作用于都是在读的操作之后的,所以下次读取数据时会是最新的数据。
基于上述分析,虽然是以一种从数据库效率考虑的问题的话虽然几率较小,但是还是会出现脏读的问题
1.当A请求时缓存刚好失效,并且有一个B请求正准备更新数据库并未生成事务
2. B请求查询到缓存失效去数据库拿到旧数据
3. A请求获取到了事务,并且提交更新数据库中的值
4. A请求删除了redis中的值
5. B请求将旧制放入到了缓存中,缓存中的数据是一个旧值
通过上述流程我们会发现这种几率是比较小的,但是也是会存在这个问题,基于这个问题我们还是可以引入上面缓存双删的一个优化。
同时阿里其实也提供了一套插件 alibaba- canal,或者使用mysql 自带的canal对mysql 的binlog 文件进行一个监听分析,从中提取中key 放入队列中去进行一个redis的删除操作,后续将会写一篇专门介绍mysql canal 和 alibaba -canal的使用
总结
例如:以上就是今天要讲的内容,本文介绍了会出现缓存不一致的3种场景,在代码开发中我们应该尽量的避免使用(update mysql ->update redis)这类情况,如果对cannal不熟悉,可以使用延迟缓存双删除方案能够满足大部分应用了,如有写有错误还希望大家能够多多点评。
版权归原作者 骑不快的小欧同学 所有, 如有侵权,请联系我们删除。