缓存雪崩、缓存击穿和缓存穿透是Redis使用过程中常见的3种问题,本文将介绍它们的相关概念、产生的原因以及对应的解决办法。
缓存雪崩
概念
所谓的缓存雪崩
是指某一时刻出现大规模的缓存失效,导致大量应当访问缓存的请求,直接去访问数据库了。此时会给数据库造成巨大的压力,甚至会导致数据库服务器的宕机。
关键词:
- 某一时刻,大规模的缓存失效
- 大量的请求,直接访问数据库
分析原因
造成缓冲雪崩的原因:
某一时刻(同一时间点)大规模的key失效了
而这背后深层次的原因可能如下:
- 这些key设置了相同的过期时间
- Redis宕机了
解决办法
针对由于设置了相同的过期时间而引起的雪崩,可以通过原过期时间加 随机值 的方式进行避免。这种方式可以称之为 缓存失效时间散列。
如果是因为redis宕机而引起的雪崩,方案是提高redis的可用性。具体的方法有:
搭建redis集群,提高redis的容灾性。
缓存击穿
概念
缓存击穿是指:
某一个热点key的缓存失效了,同时有大量的请求涌入,以至于所有请求全部打到数据库,给数据库造成巨大的压力。
缓存击穿的图形示例如下:
关键词:
- 某一个热点key缓存失效
- 高并发的场景下
- 数据在缓存中不存在,在 数据库中是存在的
解决办法
- 设置key永不过期
这种方法简单粗暴。但是并非所有的业务场景都太适合。
- 互斥锁
在缓存miss的情况下,如果需要查询数据库,必须先获得互斥锁,然后从数据库中获取数据,最后释放互斥锁,并返回数据。
互斥锁方案的实现逻辑大体如下:
上述的互斥锁方案,需要在CacheProvider之类的服务中实现。适当的封装可以简化缓存的使用。
当有多个请求发现key的缓存失效时,会去争抢互斥锁,第一个获取锁的请求会从db中查询数据,并更新缓存。
其他请求会等待一段时间,然后重新查询缓存中的key是否命中,如果命中则直接获取数据,否则尝试获取互斥锁。
互斥锁的粒度:只针对当前key进行加锁,所以影响范围可控。
缓存穿透
概念
如果一个请求查询的本就是不存在的数据。那么key必然不会命中缓存,请求会继续访问数据库,但是本次数据库查询没有查到任何记录。 像这种由于数据库中数据不存在而导致的缓存不命中 称之为 缓存穿透。
如果这样的请求大量存在,则会对数据库造成巨大压力,严重的可能会导致数据库的不可用。
之所以称之为【穿透】,是因为每次请求都会发生两次数据访问:①访问缓存(每次都miss);②访问数据库(数据不存在)。其中的缓存似乎被穿透了,不再有效。
其过程如下:
关键词:
- 数据在数据库中不存在
- 每次都要查询数据库
- 因为没有数据,所以不会缓存数据
解决办法
- 做好数据校验
为了避免发生【缓存穿透】,首先需要加强对请求参数的校验,对于明显有错误的数据,立即进行拦截返回。
例如:如果请求的参数是id,但是传入的是小于0的数值,需要进行错误提示并终止请求。
- 缓存空数据
对于在数据库中查询不到的数据,可以考虑在缓存中记录该空值(value = null),并设置一个较短的失效时间(例如30s)。
这样一方面可以保证短时间内如果出现对该key的大量请求,可以全部走缓存。另一方面也可以保证后续一但key在数据中存在了,缓存里的数据也可以得到更新。
- 布隆过滤器
使用布隆过滤器是一个比较常见的方法。布隆过滤器加在缓存和请求之间,它主要用来过滤那些一定不存在的key。
选择使用它,是因为它的空间效率和时间效率相对其他算法要好,性价比很高。不过也有缺点:存在一定的误判(有的地方说是1%)。
布隆过滤器的使用可参考如下文章:
总结
缓存雪崩、缓存击穿和缓存穿透其实有很多的相似点,所以很容易产生混淆。下面以表格的形式对其进行比较:
缓存雪崩 | 缓存击穿 | 缓存穿透 | |
---|---|---|---|
产生原因 | 大量key同时失效 & 失效key有请求 | 热点key 失效 & 对热点key有大量请求 | key 在缓存和数据库中都不存在 |
高并发请求 | 可能 | √ | 可能 |