redis缓存一致性方案
工作中经常会用redis来缓存热点数据,可以减轻底层数据库的压力,但是一旦涉及到数据更新:数据库和缓存更新,就容易出现缓存(Redis)和数据库(MySQL)间的数据一致性问题。
一.缓存同步方式
- 1.先删除缓存,再写库
- 2.先写MySQL数据库,再删除Redis缓存
二.存在的问题
两种同步方式都存在问题,举例说明如下:
- 如果删除了缓存Redis,还没有来得及写库MySQL,另一个线程就来读取,发现缓存为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。
- 如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况。
因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不一致的问题。如来解决?这里给出两个解决方案,先易后难,结合业务和技术代价选择使用。
三.三种解决方案
1.延时双删策略
思路
在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。
伪代码如下:
public void write(String key,Object data){
redis.delKey(key);
db.updateData(data);
Thread.sleep(500);
redis.delKey(key);
}
具体的操作步骤如下:
- 1.先删除缓存
- 2.再写数据库
- 3.休眠500毫秒
- 4.再次删除缓存
那么,这个500毫秒怎么确定的,具体该休眠多久呢?
需要评估自己的项目的读数据业务逻辑的耗时。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
当然这种策略还要考虑redis和数据库主从同步的耗时。最后的的写数据的休眠时间:则在读数据业务逻辑的耗时基础上,加几百ms即可。比如:休眠1秒。
该方案的弊端
当第一步删除缓存后,如果存在另一个读请求造成的缓存脏数据时。会在一段时间内数据存在不一致,而且增加了更新缓存操作的请求耗时。
2.设置缓存过期时间
思路
将缓存设置过期时间,先删除缓存,再更新数据库的数据
该方案的弊端
最坏的情况下,存在脏数据,会导致缓存过期时间内数据不一致
3.异步更新缓存(基于订阅binlog的同步机制)
思路
首先将全量数据写入到redis中,并订阅 MySQL binlog 消息,增量数据更新到redis,这里说的增量,指的是mysql的update、insert、delete变更数据。
其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。
这里可以结合使用canal(阿里的一款开源框架),通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。
引用:
1.https://zhuanlan.zhihu.com/p/95476839