转自:https://segmentfault.com/a/1190000023925209,https://blog.csdn.net/weixin_39530557/article/details/110860066
1.缓存一致性问题
是指缓存和数据库数据不同步的问题。比较依赖缓存的过期和更新策略。一般会在数据发生更改的时,主动更新缓存中的数据或者移除对应的缓存。
不管是先写MySQL数据库,再删除Redis缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况:
- 如果删除了缓存Redis,还没有来得及写库MySQL,另一个线程就来读取,发现缓存为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。
- 如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况。【即上图第3行】
因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不一致的问题。
2.缓存类型
按照Redis缓存是否接受写请求,可以分为只读缓存和读写缓存。
2.1 只读缓存
使用只读缓存时,是先把修改写到后端数据中,再把缓存中的数据删除。下次访问时,再从后端数据库读取。
- 优点:数据库和缓存完全一致,缓存中永远保留的是经常访问的热点数据。
- 缺点:数据删除后访问会触发一次缓存缺失,从后端数据库加载数据到缓存中,这个过程访问延时会变大。
2.2 读写缓存
读和写的请求都会发到缓存处理。最新的数据是在Redis中,但redis是内存数据库,如果宕机则数据丢失,所以,根据业务应用对数据可靠性和缓存性能的不同要求,会有两种策略,分别是同步直写和异步写回。
- 同步直写,优先保证数据可靠性:写请求发给缓存,同时也会发给后端数据库进行处理,等到缓存和数据库都写完数据,才给客户端返回。【能够保证一致性】但在高并发场景下,可能会导致缓存和数据库的不一致。
- 异步写回,优先提供快速响应:所有写请求都先在缓存中处理,等到这些增改的数据要被缓存淘汰时,缓存再写回后端数据库。【只修改缓存,淘汰时再写库】
总结:
- 只读缓存牺牲一定性能,优先保证数据库和缓存的一致性,更适合对于一致性要求比较高的业务场景。
- 对于数据库和缓存一致性要求不高,或者不存在并发修改同一个值的情况,使用读写缓存比较合适,保证更好的性能。
3.解决方案
3.1 延时双删
https://zhuanlan.zhihu.com/p/467410359,讲的并不清晰,画的图不解释。。。
def update_data(key, obj): del_cache(key) # 删除 redis 缓存数据。 update_db(obj) # 更新数据库数据。 logic_sleep(_time) # 当前逻辑延时执行。 del_cache(key) # 删除 redis 缓存数据。
但为什么要先删除缓存?以及为什么要延时?为什么要第二次删除缓存?好多疑问。
延时:为了redis和mysql数据同步?那为什么还要删第二次?
缺点:
- 有等待环节,如果系统要求低延时,这种场景就不合适了;
- 不适合“秒杀”这种频繁修改数据和要求数据强一致的场景;
- 延时时间是一个预估值,不能确保 mysql 和 redis 数据在这个时间段内都实时同步或持久化成功了。
3.2 设置过期时间
redis 的定位是缓存热点数据,热点数据应该设置过期时间,当数据过期后,redis 会自动淘汰,这样当业务服务节点从 redis 查询已淘汰的数据时,查询不到数据,会重新从 mysql 数据库读取数据写入 redis。
3.3 异步更新缓存(基于订阅binlog的同步机制)
- 读Redis:热数据基本都在Redis
- 写MySQL:增删改都是操作MySQL
- 更新Redis数据:MySQ的数据操作binlog,binlog更新到消息队列,增量实时更新到Redis,增量是指update、insert、delate等变更数据。
类似mysql的主从备份机制。消息推送工具也可以用:kafka、rabbitMQ等。