一般来说,目前的系统设计上为了缓解数据库峰值压力,都会增加 Redis 作为第一道屏障,但是其依然存在一些不足。总结起来是三大问题,分别是缓存雪崩、缓存击穿和缓存穿透。本文旨在说清楚三个问题的原因及相应的防范策略。
以 Redis 为代表的 NoSQL 之所以可以缓解数据库峰值压力是因为部分请求在 Redis 上即可完成,无需真正达到数据库。而下列三大问题的原因都与缓存有关,如 Key 失效,或有意构造无效 Key 绕过缓存数据库。
一、缓存雪崩 & 缓存击穿
雪崩一般是指无法挽回的灾难,想像一下雪崩发生后再想让雪回到山上那肯定难如登天。因此缓存雪崩有两个特点,一是灾难性后果,二是较难恢复。具体描述,若某一个时刻出现大规模缓存失效的情况,就会导致大量请求直接打在数据库上,导致数据库压力骤增。在高并发的情况下,可能瞬间导致数据库宕机。这时候马上重启数据库,又会有新的请求导致数据库宕机。
缓存击穿的原理与雪崩类似,都是 Key 失效。顾名思义,击穿听起来是单点爆破,这也是它和雪崩的主要区别。雪崩是大规模的 Key 失效,击穿是一个承接大量请求的热点 Key 突然失效,导致这些请求短时间内全部打在数据库上,导致数据库压力骤增。
二、Key 的失效原因 & 预防策略
Key 失效本身不是一件意料之外的事,Redis 等 NoSQL 的自身容量有限,它们会定期清理过期的 Key。一般而言,Key 在以下情况会失效:
- Redis 升级或维护,没有备份缓存;
- 过期,部分 Key 会在一定时间后失效;
- 内存不足,当 Redis 的存储空间耗尽时,会淘汰部分 Key,淘汰策略可能包括 LRU、空闲时间上限等。
针对上述缓存问题,缓存雪崩最有可能的成因是一批 Key 在同一时间失效,因此可以考虑设计随机失效时间。在数据库端,加强其应对高并发的能力和容灾能力,如分库分表、读写分离。针对缓存击穿,可以设置热点 Key 永不过期,或加互斥锁,必须拿到锁才可访问数据库。
三、缓存穿透
实际使用 Redis 的大部分情况是通过 Key 查询对应的值,若发送请求传进来的 Key 不存在Redis中,那么就查不到缓存,只能去数据库查询。而如果有大量这样的请求,这些请求像“穿透”缓存一样直接打在数据库上,这就是缓存穿透。假如有 Hacker 传进大量的非法 Key,那么如此大量的请求打在数据库上是很致命的问题(拒绝服务攻击),因此在开发中要对参数进行校验,对于非法的参数可以直接返回错误提示。
针对缓存穿透,最简单的办法就是在缓存数据库端拦截请求,直接将非法或不存在的 Key 置为 Null 值,但这种方式比较吃内存。另一常用方案是诞生于 1970 年的布隆过滤器(Bloom Filter),至今仍在很多软件工程领域发挥作用。
四、布隆过滤器
布隆过滤器可以检查 Key 是 “可能在集合中” 还是 “绝对不在集合中”,通俗解释就是可能错杀好人,但绝不漏掉任何一个坏人。