问题描述
应对策略
把缓存 分成读写缓存 和只读缓存 。 只读缓存 :只在缓存 进行数据查找,即使用“更新数据库 +删除缓存 ”策略。 新增数据时,直接写入数据库 ; 更新(修改/删除)数据时,先删除缓存 。 后续访问这些增删改的数据时,会发生缓存 缺失,进而查询数据库 ,更新缓存 文中提到无并发情况 下,若步骤二失败会有一致性问题。 可以采用消息队列,异步重试。(多次失败后通知业务层) 也可采用binlog变更日志,根据日志更新/删除缓存 并发条件下 先删除缓存 ,再更新数据库 (其他线程在缓存 为空时访问数据库 获得旧值,顺便将缓存 更新了,造成缓存 中的值在A线程完成更新数据库 后也不再更新) 设置缓存 过期时间。淘汰缓存 失败时,过期后,读请求从db中获取数据,并更新缓存 。(?删除缓存 可能会失败,设置超时时间就保证能成功了?) 延迟双删:更新完数据库 后,等待一段时间(使确保其他线程能读到修改后的值),再删除缓存 先更新数据库 ,再删除缓存 (线程B读到旧值,?感觉问题不大;若有从库,会尝试去从库拿?,由于主从库的延迟,造成缓存 中保存的是从库中的旧值) 延迟消息:发送删除缓存 的消息到队列,延迟处理。如有必要,考虑主从数据库 延迟 通过数据库 的binlog来异步删除缓存 先更新数据库 再删除缓存 ,有可能导致请求因缓存 缺失而访问数据库 ,给数据库 带来压力,也就是缓存 穿透的问题。针对缓存 穿透问题,可以用缓存 空结果、布隆过滤器进行解决 加锁:更新数据时,加写锁;查询数据时,加读锁 读写缓存 :需要在缓存 中对数据进行增删改查,即使用“更新数据库 +更新缓存 ”策略 同步直写:使用事务,保证缓存 和数据更新的原子性,并进行失败重试 异步回写:写缓存 时不同步写数据库 ,等到数据从缓存 中淘汰时,再写回数据库 重点考虑更新数据库 和缓存 的顺序,读写还是读读 对于读写场景,延迟消息比较,发现不一致后,做业务补偿(加延迟时间?) 对于,写写场景,配合分布式锁处理。对于同一资源的操作,需要获取分布式锁,未获得锁的放在队列中。
总结
数据库 和缓存 存在不一致性,部分原因是因为数据库 和缓存 是独立的两部分。从设计上可能做了适配,但是总归有配合问题。如果数据库 本身在设计上就考虑设计高并发缓存 ,我们在使用时就不用考虑一致性问题或者性能问题。 如人为设置一个等待时间,在数据库 应该能完成数据更新的时,进行缓存 的删除。如果数据库 与缓存 是一体的,可能在内部就能完成,数据库 完成删除后通知缓存 更新。这样效率肯定比外界干预的两部效率要高 性能和准确性的取舍在因场景而异,我们只能采用该架构下我们认知中,在该场景下,在有限的时间内能够完成的最优方案。
参考:https://cloud.tencent.com/developer/article/1888803