缓存雪崩

缓存雪崩指的是大量 key 几乎同时过期,导致大量请求直接打到 DB,导致 DB 服务不可用,进而导致其它服务也不可用,就像雪崩一样

应对方式

给 Key 指定过期时间时,加上一个随机数

例如,对于 comment:likeset: 这一类 key 来说,假设预期过期时间为 120s,那么在设置 key 的 TTL 时,就可以在 120s 的基础上 +- 一个随机数,使得过期时间有略微偏差,不至于同时过期

缓存击穿

缓存击穿指的是热点 key(即访问很多的 key)过期,导致大量请求直接打到 DB,导致 DB 服务不可用,进而导致其它服务也不可用

缓存击穿是缓存雪崩的 子集

应对方式

除了缓存雪崩的应对方式外,对于缓存击穿,还可以使用「互斥锁」来应对

具体来说,读取数据时,先判断缓存是否过期,如果过期:

  • 加锁
  • 再次判断缓存是否过期:
    • 如果过期,读 DB,重建缓存
    • 否则,读缓存
  • 解锁

这样,就只有一个线程真正执行了读 DB 的操作

例如,在 Go 的并发编程中,常常使用 singleflight 来防止缓存击穿

缓存穿透

缓存穿透指的是请求 不存在的数据,由于数据不存在,必然 cache miss,导致请求直接打到 DB

如果有恶意请求,随机生成一些 query condition,那么大量的请求打到 DB,导致 DB 服务不可用,进而导致其它服务也不可用

应对方式

通常有两种方式防止缓存穿透:

  • 缓存空对象
  • 布隆过滤

缓存空对象的思想是:如果访问了一个不存在的数据,那么将这个查询条件缓存起来,下一次请求就先判断数据是否存在,存在才访问 Cache、DB

这种方式无法解决恶意请求的问题,因为查询条件是随机的

布隆过滤器可以将存在的对象「放到」过滤器中,查询时,先根据布隆过滤器判断数据是否存在,存在才访问 Cache、DB

那么,如果将一个对象放到布隆过滤器中?

事实上:布隆过滤器使用了 位图 + hash function

  • 先使用「多个」不同的哈希函数计算对象的哈希值
  • 计算存储到位图的位置:hash_val % sizeof(bitmap)

在判断数据是否存在时:

  • 使用「多个」不同的哈希函数计算对象的哈希值
  • 计算存储到位图的位置:hash_val % sizeof(bitmap)
  • 如果每一个位置都在位图中存在,那么认为这个对象存在,否则认为不存在

image

图片来自小林 Coding

布隆过滤存在 误判 的可能,因为存在哈希冲突,不同对象可以有相同的哈希值

举个例子:

  • 对象 A 的哈希值为 1、2、3
  • 对象 B 的哈希值为 2、3、4
  • 对象 C 的哈希值为 1、2、4

假设 A、B 在 DB 中存在,C 不存在,且位图的大小为 4

那么根据哈希值,可以得到位图为 1、2、3、4

由于 C 的哈希值为 1、2、4,每一位都在位图中存在,因此,布隆过滤器认为 C 存在,这就产生了误判

为了降低误判几率,可以增加哈希函数的数量,以及位图的大小