携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第17天,点击查看活动详情
为了保证缓存中的数据与数据库中的数据一致性,会给redis数据设置过期时间,当缓存过期后,用户访问的数据如果不在缓存里,业务系统会访问数据库,重新生成缓存,然后后续的请求就能命中缓存。
如何避免缓存雪崩、缓存击穿、缓存穿透?
缓存雪崩
什么是缓存雪崩?
当大量缓存在同一时间过期时,如果此时有大量的用户请求,都无法在redis中处理,就会直接访问数据库导致数据库压力骤增,甚至造成数据库宕机,从而导致系统崩溃。这就是缓存雪崩。
解决方案
- 将缓存失效时间随机打散,使每个缓存失效的过期时间不重复
- 互斥锁:保证同一时间内只有一个线程来构造缓存,构造完成后再释放锁。最好设置超时时间,避免遇到拿到锁的请求出现意外一直阻塞不释放锁,导致系统无响应。
- 双key策略:一个主key会过期,一个备用key不会过期,value值相同,相当于缓存数据做了副本。访问不到主key就访问备用key,在更新缓存的时候同时更新两个key的数据。
- 后台更新缓存:设置缓存不过期,通过后台服务来更新缓存,避免因失效导致缓存雪崩。事实上,即使不过期,在内存
Redis故障宕机也会引起缓存雪崩问题
- 服务熔断或请求限流机制。服务熔断:停止处理请求,但是全部业务无法工作,所以采用请求限流机制,将少部分请求发送到数据库,更多的请求拒绝在入口处。等到redis正常并且缓存预热后解除。
- 构建redis缓存高可靠集群
缓存击穿
热点数据:频繁的被访问的数据,比如秒杀活动中的商品信息。
什么是缓存击穿问题?
如果缓存中的某个热点数据过期,大量的请求访问热点数据时,无法从缓存中拿到,就会直接访问数据库,数据库容易被高并发的请求冲垮,这就是缓存击穿。
解决方案:
(缓存击穿可以看成是缓存雪崩的子集)
- 互斥锁方案(Redis 中使用 setNX 方法设置一个状态位,表示这是一种锁定状态),保证同一时间只有一个业务线程请求缓存,未能获取互斥锁的请求,要么等待锁释放重新读取,要么返回空值或默认值。
- 不设置过期时间。或者过期前通知后台线程更新缓存以及重新设置过期时间。
缓存穿透
如果只是缓存雪崩、击穿,数据库中还是保存了要访问的数据,一旦缓存回复就可以减轻数据库压力,但缓存击穿不一样。
什么是缓存击穿?
要访问的数据既不在缓存中,也不在数据库中,导致请求发现缓存缺失后,即使访问数据库,也没有找到要访问的数据构建不了缓存,来服务后续的请求,当有当量相同的请求,导致数据库压力骤增。这就是缓存穿透。
导致这种情况发生一般有两种可能:
- 业务误操作,误删了数据
- 黑客恶意攻击,故意访问大量不存在数据的业务。
解决方案:
- 非法请求的限制:当有大量恶意请求访问不存在数据的时候,会发生缓存穿透,因此在API入口处要判断请求参数,如果是恶意请求就返回错误。
- 设置空值或者默认值: 如果业务发现缓存穿透的现象的话,针对查询的数据在缓存中设置一个空值或者默认值。下次就会返回空值或者默认值
- 使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断是否存在。在写入数据库数据时,使用布隆过滤器做个标记,在用户请求到来时,先确认缓存有没有,没有的话查询布隆过滤器快速判断数据是否存在,不存在就不用访问数据库。
布隆过滤器
布隆过滤器由「初始值都为 0 的位图数组」和「 N 个哈希函数」两部分组成。当我们在写入数据库数据时,在布隆过滤器里做个标记,这样下次查询数据是否在数据库时,只需要查询布隆过滤器,如果查询到数据没有被标记,说明不在数据库中。
布隆过滤器会通过 3 个操作完成标记:
- 第一步,使用 N 个哈希函数分别对数据做哈希计算,得到 N 个哈希值;
- 第二步,将第一步得到的 N 个哈希值对位图数组的长度取模,得到每个哈希值在位图数组的对应位置。
- 第三步,将每个哈希值在位图数组的对应位置的值设置为 1;
举个例子,假设有一个位图数组长度为 8,哈希函数 3 个的布隆过滤器。
在数据库写入数据 x 后,把数据 x 标记在布隆过滤器时,数据 x 会被 3 个哈希函数分别计算出 3 个哈希值,然后在对这 3 个哈希值对 8 取模,假设取模的结果为 1、4、6,然后把位图数组的第 1、4、6 位置的值设置为 1。当应用要查询数据 x 是否数据库时,通过布隆过滤器只要查到位图数组的第 1、4、6 位置的值是否全为 1,只要有一个为 0,就认为数据 x 不在数据库中。
布隆过滤器由于是基于哈希函数实现查找的,高效查找的同时存在哈希冲突的可能性,比如数据 x 和数据 y 可能都落在第 1、4、6 位置,而事实上,可能数据库中并不存在数据 y,存在误判的情况。
所以,查询布隆过滤器说数据存在,并不一定证明数据库中存在这个数据,但是查询到数据不存在,数据库中一定就不存在这个数据。