数据库和缓存的数据一致性 -20241124

发布于:2024-11-28 ⋅ 阅读:(18) ⋅ 点赞:(0)

问题描述

  • 一致性
    • 缓存中有数据,缓存的数据值=数据库中的值
    • 缓存中本没有数据,数据库中的值=最新值(有请求查询数据库时,会将数据写入缓存,则变为上面的“一致”状态)
  • “数据不一致”:
    • 缓存的数据值≠数据库中的值;
    • 缓存或者数据库中存在旧值,导致其他线程读到旧数据。

应对策略

  • 把缓存分成读写缓存和只读缓存。
    • 只读缓存:只在缓存进行数据查找,即使用“更新数据库+删除缓存”策略。
      • 新增数据时,直接写入数据库;
      • 更新(修改/删除)数据时,先删除缓存。
      • 后续访问这些增删改的数据时,会发生缓存缺失,进而查询数据库,更新缓存
      • 文中提到无并发情况下,若步骤二失败会有一致性问题。
        • 可以采用消息队列,异步重试。(多次失败后通知业务层)
        • 也可采用binlog变更日志,根据日志更新/删除缓存
      • 并发条件下
        • 先删除缓存,再更新数据库(其他线程在缓存为空时访问数据库获得旧值,顺便将缓存更新了,造成缓存中的值在A线程完成更新数据库后也不再更新)
          • 设置缓存过期时间。淘汰缓存失败时,过期后,读请求从db中获取数据,并更新缓存。(?删除缓存可能会失败,设置超时时间就保证能成功了?)
          • 延迟双删:更新完数据库后,等待一段时间(使确保其他线程能读到修改后的值),再删除缓存
        • 先更新数据库,再删除缓存(线程B读到旧值,?感觉问题不大;若有从库,会尝试去从库拿?,由于主从库的延迟,造成缓存中保存的是从库中的旧值)
          • 延迟消息:发送删除缓存的消息到队列,延迟处理。如有必要,考虑主从数据库延迟
          • 通过数据库的binlog来异步删除缓存
          • 先更新数据库再删除缓存,有可能导致请求因缓存缺失而访问数据库,给数据库带来压力,也就是缓存穿透的问题。针对缓存穿透问题,可以用缓存空结果、布隆过滤器进行解决
          • 加锁:更新数据时,加写锁;查询数据时,加读锁
    • 读写缓存:需要在缓存中对数据进行增删改查,即使用“更新数据库+更新缓存”策略
      • 同步直写:使用事务,保证缓存和数据更新的原子性,并进行失败重试
      • 异步回写:写缓存时不同步写数据库,等到数据从缓存中淘汰时,再写回数据库
    • 重点考虑更新数据库和缓存的顺序,读写还是读读
      • 对于读写场景,延迟消息比较,发现不一致后,做业务补偿(加延迟时间?)
      • 对于,写写场景,配合分布式锁处理。对于同一资源的操作,需要获取分布式锁,未获得锁的放在队列中。

总结

  • 数据库和缓存存在不一致性,部分原因是因为数据库和缓存是独立的两部分。从设计上可能做了适配,但是总归有配合问题。如果数据库本身在设计上就考虑设计高并发缓存,我们在使用时就不用考虑一致性问题或者性能问题。
    • 如人为设置一个等待时间,在数据库应该能完成数据更新的时,进行缓存的删除。如果数据库与缓存是一体的,可能在内部就能完成,数据库完成删除后通知缓存更新。这样效率肯定比外界干预的两部效率要高
  • 性能和准确性的取舍在因场景而异,我们只能采用该架构下我们认知中,在该场景下,在有限的时间内能够完成的最优方案。

参考:https://cloud.tencent.com/developer/article/1888803