缓存穿透和缓存击穿

什么是缓存穿透和缓存击穿

缓存穿透和缓存击穿是存在区别的两种情况:

  • 缓存穿透

要查询的数据本来就不存在于缓存,也不存在于数据库。当各种请求进来查询的时候,先查缓存,没有查到就会去持久层查询数据库,依旧查不到。下一个请求再来查的时候还是相同的情况。

在这种情况下,缓存这一层相当于不存在,所有请求直接查询了数据库

  • 缓存击穿

如果缓存中的数据因为某种原因失效(存活时间到期或者被删除等),并发查询这条数据的请求在失效的瞬间就会穿过缓存去查询数据库,就像在缓存这层开了个洞,所以被成为缓存击穿

以上这两种情况在并发量大的情况下会对数据库造成很大压力,甚至可能引起数据库宕机

面对缓存穿透一些解决办法

  • 缓存空值

面对缓存穿透的情况,这类数据因为本来就不存在,所以最终请求到的结果也是空值,所以可以将对应的 key 直接存入缓存,value 相应设置为 null 或者空字符串(根据业务)

为了保证这些缓存数据不会对后期真实存在了的数据数据造成影响,需要对这类缓存设置一个相对较短的过期时间,而且应该保证在真正的数据入库成功之后,将这些缓存失效

因为这种方法在有效数据入库成功之后和使空值缓存失效之前,存在一定的时间差,还是可能造成一段时间的数据不一致

而且将空值存入缓存也会占用缓存资源,造成内存浪费。如果遭受恶意攻击,将大量空值存入缓存,可能会将内存资源占满,造成缓存服务器一定时间无法访问

这种解决方案的建议使用场景为: key 全集数据数据量级较小,并且完全可预测,可以通过提前填充的方式直接将数据缓存

  • 布隆过滤器(BloomFilter)

本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你”某样东西一定不存在或者可能存在”

相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的

通常把有数据的 key 都放到 BloomFilter 中,每次查询的时候都先去 BloomFilter 判断,如果没有就直接返回空

由于布隆过滤器不支持删除操作,对于删除的 key,查询就会经过 BloomFilter 然后查询缓存再查询数据库,所以 BloomFilter 建议结合缓存空值用,对于删除的 key,可以在缓存中缓存空

同样它也不支持扩容操作,这就要求布隆过滤器建立初期必须进行严格的推算,确保后期不需要扩容,否则重建布隆过滤器的成本可能会超乎想象

缓存击穿的一些解决办法

  • 互斥锁(独占锁)

在缓存失效的时候,并不直接去查询数据库,而是要拿到锁,由成功抢占到锁资源的线程去查询数据库,并更新回缓存,更新缓存成功,释放锁资源,后续

这样可以避免多个线程同时穿过缓存去查询数据库,防止多个线程对缓存进行重复更新的情况发生