一、操作
我们在实际项目中经常会使用到Redis缓存用来缓解数据库压力,但是当更新数据库时,如何保证缓存及数据库一致性,一般我们采用延时双删策略。
目前系统中常用的做法是一个查询接口,先查询Redis,如果不存在则查询数据库,并将结果放入到Redis中。
为什么是删除缓存,而不是更新缓存呢?主要是如果缓存的内容是带有树型结构或者List,Map,那么更新其中一个内容相对较慢。
本文所讲操作,均是按照如果缓存不存在,查询数据库后,再放入Redis。
二、 常见更新策略
-
1 先删缓存,再更新数据库
-
2 先更新数据库,再删除缓存
-
3 普通双删
-
4 延迟双删
三、图解
1 、先删缓存,再更新数据库
线程A删除缓存数据,此时还没更新数据库
线程B查询缓存没有数据,查询数据库还是旧数据,放入缓存
线程C及其他线程使用旧缓存数据,缓存和数据库不一致
2、先更新数据库,再删除缓存
线程A更新数据库,此时还没有删除缓存
线程B及其他线程此时使用的还是旧缓存数据,和数据库内容不一致
3 、普通双删
线程A先删除缓存,再更新数据库,再删除缓存
线程B查询缓存没有数据,在线程A更新数据库之前,查询到旧数据,此时系统时间片切换到线程A执行删除缓存,之后又轮到线程B放入缓存旧数据
线程C针对于线程A,查询缓存没有数据,查询到旧数据,放入缓存旧数据
都不能满足缓存和数据一致性。
4、延迟双删
线程A先删除缓存,之后更新数据库
线程B和线程C发现缓存没数据,查询数据库。线程B查询到的是旧数据,线程C查询到的是新数据。之后纷纷放入缓存
线程A延时3-5秒(时间一般要大于SQL执行时间+线程切换执行时间100ms足够),再将缓存删除。之后其他线程再查询缓存,发现没数据,再次查询数据库及放入缓存都是新数据
极端情况就是线程D,所以延时双删还是不一定能保证缓存及数据一致。
四、总结:
1、加锁
在发现缓存没有数据后,在执行查询数据库前,对该Key进行加锁,查询数据库并放入缓存后再解锁,这样可以避免缓存击穿问题,当某个redis数据不存在时,大量线程并发查询数据库。
在需要执行双删前,对该Key进行加锁,之后执行删除缓存,更新数据库,放入新数据到缓存,在解锁。保证缓存和数据一致性。
加锁的Key都需要设置过期时间,避免因为宕机造成死锁。
2、使用Canal 分布式数据库同步工具,结合mq中间件