如何让缓存失效?90%的人会回答错误


写在前面

这是一个常见的面试题,但是真正能完整回答出来的人少之又少。今天看了一篇文章,发现我之前的思路也是有问题的。作者记录一下,大家一起学习。

常见策略

最常见的策略无非就两种:1.先删除缓存再更新db;2.先更新db再失效缓存。

注意,这里没有更新缓存的操作,只有失效缓存。这是为了防止并发,如果更新缓存,并发的时候容易将脏数据更新到缓存。

第一种:先删除缓存再更新db有什么问题

当读写事务并发的时候

写事务1 读事务2
1.删除缓存
1.缓存读取失败,从db读
2.更新缓存
2.写db

写事务肯定比读事务慢,上述读写事务这种交叉执行是很容易出现的。只要读事务在写事务删除缓存之后执行,大概率会在写事务更新db之前执行完。这个时候就造成了缓存和db不一致。

显然这是一个错误的操作,面试的时候会减分。

第二种:先更新db再删除缓存有什么问题

这个答案是正确的。但是这个操作也会存在问题。只是问题出现的条件极其苛刻,一般可忽略。除非对一致性要求非常高的场景。我们先来看看什么情况下会有问题。

还是读写事务并发的时候

写事务1 读事务2
1.读缓存失败(缓存过期),从db中读取
1.写db
2.删缓存
2.写缓存

根据上述2个事务执行顺序,最终导致缓存里面是旧数据。上述问题出现的2个必要条件是:

  • 1.刚好缓存过期。这个条件很容易出现
  • 2.写事务要在读事务读缓存失败并且从数据库中读取到旧数据之后开始,并且要在读事务写缓存之前结束。这个条件是很难出现的。读事务整个操作一般都比写事务执行的快,并且从db读取之后就会立马写缓存。中间这么短的时间要执行一个写事务并且删缓存的操作是很难的。

但是难出现并不代表不出现,为了安全起见,缓存一般都要设置过期时间。万一出现不一致,等过期时间过了就没有问题了。

非常见策略

第一种: Read/Write Through

Read Through

在查询操作中更新缓存,也就是说,当缓存失效的时候(过期或LRU换出),则用缓存服务自己来加载,从而对应用方是透明的。
Write Through

当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库(这是一个同步操作)

第二种:Write Behind Caching

在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。这个设计的好处就是让数据的I/O操作飞快无比(因为直接操作内存嘛 ),因为异步,它还可以合并对同一个数据的多次操作,所以性能的提高是相当可观的。

但是,其带来的问题是,数据不是强一致性的,而且可能会丢失(我们知道Unix/Linux非正常关机会导致数据丢失,就是因为这个事)。在软件设计上,我们基本上不可能做出一个没有缺陷的设计,就像算法设计中的时间换空间,空间换时间一个道理,有时候,强一致性和高性能,高可用和高性性是有冲突的。软件设计从来都是取舍Trade-Off。

另外,Write Back实现逻辑比较复杂,因为他需要track有哪数据是被更新了的,需要刷到持久层上。操作系统的write back会在仅当这个cache需要失效的时候,才会被真正持久起来,比如,内存不够了,或是进程退出了等情况,这又叫lazy write。

总结

有的人会说如果更新db成功了,删除缓存失败了怎么办?上述讨论的都是基于更新db和删除缓存都会成功的情况。

删除缓存失败了可以重试,或者在更新db之前加一个删除缓存,确保缓存系统是可访问的,这样在更新db之后删除缓存的时候大概率不会有问题。即:删除缓存-》更新db-》删除缓存。

另外,记得给缓存设置过期时间,确保在出现问题的时候让出问题的时间尽可能的短。

参考

1]缓存更新的套路


文章作者: Alex
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Alex !
  目录