缓存的使用姿势:缓存穿透了怎么办

 
 
对于缓存来说:
命中率是它的生命线
 
一般来说:
  • 我们的核心缓存的命中率要保持在 99% 以上
  • 非核心缓存的命中率也要尽量保证在 90%
  • 如果低于这个标准,那么你可能就需要优化缓存的使用方式了。
 

什么是缓存穿透

缓存穿透其实是指从缓存中没有查到数据,而不得不从后端系统(比如数据库)中查询的情况。
 

缓存穿透的原因

  • 互联网数据量极大,缓存系统容量有限
  • 互联网数据访问遵循 80/20 原则,又称为帕累托法则。重要的事物只占 20%,剩余 80% 是不重要的。在数据访问领域,我们经常访问 20% 的热点数据,而另外 80% 的数据则不会被访问
  • 缓存穿透不可避免。即我们在有限的缓存空间存储 20% 的热点数据就可以有效保护后端系统,也就是放弃缓存另外 80% 的非热点数据了。
 

什么样的缓存穿透对系统有害

大量的穿透请求超过了后端的承受范围,从而造成了后端系统的崩溃。
 

缓存穿透的解决方案

场景

TL;DR

使用 Cache Aside,查询系统中不存在的用户。

完整描述

在你的电商系统的用户表中,我们需要通过用户 ID 查询用户的信息,缓存的读写策略采用 Cache Aside 策略。 如果要读取一个用户表中未注册的用户,会发生什么情况呢?按照这个策略,我们会先读缓存,再穿透读数据库。由于用户并不存在,所以缓存和数据库中都没有查询到数据,因此也就不会向缓存中回种数据(也就是向缓存中设置值的意思),这样当再次请求这个用户数据的时候还是会再次穿透到数据库。在这种场景下,缓存并不能有效地阻挡请求穿透到数据库上,它的作用就微乎其微了。

回种空值

当我们从数据库中查询到空值或者异常时,向缓存中回种一个空值。而通常来说,我们又会给这个空值加一个较短的过期时间。
采用这个方案时,应该先评估一下缓存容量是否能够支撑。如果需要大量缓存即诶安来支持,那么就无法通过回种空值来解决,这时就应该考虑布隆过滤器。

布隆过滤器

布隆在 1970 年提出的布隆过滤器算法,用来判断一个元素是否在集合中。这种算法由一个二进制数组和一个 Hash 算法组成。
基本思路如下:
我们把集合中的每一个值按照提供的 Hash 算法算出对应的 Hash 值,然后将 Hash 值对数组长度取模后得到需要计入数组的索引值,并且将数组这个位置的值从 0 改成 1。在判断一个元素是否存在于这个集合中时,你只需要将这个元素按照相同的算法计算出索引值,如果这个位置的值为 1 就认为这个元素在集合中,否则则认为不在集合中。
notion image
 

优点:

  • (false positive) 布隆过滤器判断元素不在集合中时,那么这个元素一定不在集合中。 这一点非常适合解决缓存穿透问题。

缺点:

  • 判断元素是否在集合中时有一定错误几率,比如它会把不是集合中的元素判断为处在集合中
    • 原因:Hash 碰撞问题。由于 Hash 的碰撞造成了两个用户 ID,A 和 B 会计算出相同的 Hash 值。
  • 不支持删除元素
    • 原因:Hash 碰撞问题。假设 A 和 B 都是集合中的元素,它们有相同的 Hash 值,就会映射到同一个位置。这时如果删除了 A,数组中对应位置的值也从 1 → 0,那么判断 B 的时候发现值是 0,也会判断 B 是不在集合中的元素,造成了错误的结论。
      如何解决:存储一个计数。比如 A 和 B 同时命中一个索引,那么这个位置的值就是 2,如果 A 被删除了就把这个值从 2 改成 1。这个方案中的数组不再存储 bit 位,而是存储值,就会增加空间消耗。
      适用场景:如用户注册场景,用户删除的情况基本不存在,所以可以适用布隆过滤器来解决缓存穿透问题。

优化和建议

  1. 选择多个 Hash 函数计算多个 Hash 值,减少误判几率
  1. 布隆过滤器会消耗内存空间,所以需要评估在具体业务场景下需要多大内存,以及成本是否可接受

Dog-pile effect(狗桩效应)

场景:
有一个极热点的缓存项,它一旦失效会有大量请求穿透到数据库,这会到数据库造成瞬时的极大压力。
如何解决:
  • 思路:尽量减少缓存穿透后的并发
  • 代码中,控制在某个热点缓存失效后启动一个后台线程,穿透到数据库,将数据加载到缓存中。在缓存未加载之前,所有访问这个缓存的请求都直接返回而不再穿透。
  • 设置分布式锁,只有获取到锁的请求才能穿透到数据库。
 

总结

  1. 回传空值是最常见的解决方案,实现非常简单。如果评估空值缓存占空间可以接受,可以优先使用这一种方案
  1. 布隆过滤器会引入新的组件,同时引入新的开发和运维上的成本。所以只有在存在海量查询数据库中,不存在数据的请求时才会使用,使用时需要关注布隆过滤器对内存的消耗。
  1. 对于极热点缓存数据穿透造成的 狗桩效应 ,可以通过设置分布式锁或者后台线程定时加载的方式解决。
 

核心思想

  • 数据库是个脆弱的资源,相比缓存处于绝对劣势
  • 核心目标在于减少对数据库的并发请求
 

思考

日常工作中使用到的缓存穿透解决方案
//TODO