如何保证数据库和缓存的一致性

10 分钟阅读 1311 字 + 723 词
删除缓存的方法:
读取数据的逻辑:
  1. 去缓存查数据
  2. 如果缓存查询数据失败,就查询数据库
  3. 数据库获取到数据,再更新缓存
  4. 再返回
写数据的逻辑: 有两种思路,都是保证 最终一致性
  • 先删除缓存再更新数据库
    • 可能会造成数据不一致 ,假设线程1先删除缓存,然后在更新数据库的过程中 发生了网络延迟 。这个时候还有线程2进行读操作,首先由于缓存已经被删了,所以缓存未命中,然后读数据库,将旧的数据更新到了缓存。线程1的网络好了,然后更新数据库。但是这个时候缓存和数据库的数据发生了不一致。
    • 解决办法:使用 延迟双删 ,写操作 更新了数据库后停顿一个时间再进行删除缓存 。这样子最多其他线程在这个时间段里读到的是脏数据。
  • 先更新数据库再删除缓存(cache aside)
    • 假如在删除缓存时失败,那就用重试机制。
    • 在高并发的时候,重试最好的方法是异步,比如发送消息给mq中间件,实现异步解耦。
用redis一定是为了提升性能,所以这里所有的方案都是 最终一致性 ,允许短暂的不同,如果非要强一致,那就不要用缓存或者加锁或者通过分布式读写锁。
参考答案:
Cache Aside (先更新数据库再删除缓存)
Cache Aside 是最基本的缓存模式,在这个模式下,业务代码就是== 把缓存看成是和数据库一样的独立的数据源 ==,然后业务代码控制怎么写入缓存,怎么写入数据库。一般来说,都是 优先写入数据库 的。
不管是先写数据库还是先写缓存, Cache Aside 都不能解决数据一致性问题。
  • 原理
    • 读:== 先从缓存中读取数据,如果没有就再去数据库里面读数据,然后把数据放回缓存中,如果缓存中可以找到数据就直接返回数据; ==
    • 写:== 更新数据的时候先把数据持久化到数据库(先),然后再让缓存失效(后)。 ==
  • 问题 :假如有两个操作一个更新一个查询,第一个操作先更新数据库,还没来及删除缓存,查询操作可能拿到的就是旧的数据;更新操作马上让缓存失效了,所以后续的查询可以保证数据的一致性;还有的问题就是有一个是读操作没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,也会造成脏数据, 出现了缓存中数据和数据库数据不一致。
  • 可行性 :出现上述问题的概率其实非常低,需要同时达成读缓存时缓存失效并且有并发写的操作。数据库读写要比缓存慢得多,所以读操作在写操作之前进入数据库,并且在写操作之后更新, 概率比较低
  • 解决方法依旧使用 延迟双删
Read/Write Through (将缓存作为整个存储的代理)
read through 它的核心是== 当缓存里面没有数据的时候,缓存会代替你去数据库里面把数据加载出来 ==,并且缓存起来。可以异步更新缓存
Write Through 就是在== 写入数据的时候,只写入缓存 ,然后 缓存会代替我们的去更新数据库 ==。但是,Write Through 没有要求先写数据库还是先写缓存,不过一般也是先写数据库。
  • 原理 Read/Write Through原理是把更新数据库(Repository)的操作由缓存代理,应用认为后端是一个单一的存储,而存储自己维护自己的缓存。
  • Read Through :就是在 查询操作中更新缓存 ,也就是说,当缓存失效的时候,== Cache Aside 策略是由 调用方负责 ==把数据加载入缓存,而==Read Through则 用缓存服务自己来加载 ,从而对调用方是透明的==。
  • Write Through :当有 数据更新的时候,如果没有命中缓存,直接更新数据库 ,然后返回。如果命中了缓存,则更新缓存,然后再由缓存自己更新数据库(这是一个同步操作)。
Write Behind
  • 原理 在更新数据的时候,只更新缓存,不更新数据库,而缓存会异步地批量更新数据库或者缓存过期的时候再刷到数据库。 这个设计的好处就是 让数据的I/O操作非常快 ,带来的问题是,数据 不是强一致性的 ,而且可能会丢。
  • 第二步失效问题 :这种可能性极小,缓存删除只是标记一下无效的软删除,可以看作不耗时间。如果会出问题,一般程序在写数据库那里就没有完成:故意在写完数据库后,休眠很长时间再来删除缓存。