07-Redis 穿透,击穿,雪崩,倾斜,淘汰,删除
1. 缓存穿透
- 概念
查不到,缓存层+持久层都压力增大
当用户去查询数据的时候,发现 redis 内存数据库中没有,于是向持久层数据库查询,发现也没有,于是查询失败,当用户过多时,缓存都没有查到,于是都去查持久层数据库,这会给持久层数据库造成很大的压力,此时相当于出现了缓存穿透。
- 解决方案 × 2
布隆过滤器
(★):是一种数据结构,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。
5TB的硬盘上放满了数据,请写一个算法将这些数据进行排重。如果这些数据是一些32bit大小的数据该如何解决?如果是64bit的呢?
对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。
- Bitmap: 典型的就是哈希表
缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。- 布隆过滤器
就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。
它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。
Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,可以多引入几个Hash,如果通过其中的一个Hash值得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的Hash函数告诉该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。
Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。
布隆过滤器基本使用
布隆过滤器有二个基本指令,bf.add
添加元素,bf.exists
查询元素是否存在,如果想要一次添加多个,就需要用到 bf.madd
指令。同样如果需要一次查询多个元素是否存在,就需要用到 bf.mexists
指令。
1 |
|
缓存空对象
:当存储层查不到时,即使返回的空对象也将其缓存起来,同时设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护后端数据。
但会有两个问题:
如果空值被缓存起来,就意味着需要更多的空间存储更多的键,会有很多空值的键。
即使对空值设置了过期时间,还是会存在缓存层和存储层会有一段时间窗口不一致,这对于需要保持一致性的业务会有影响。
2. 缓存击穿
- 概念
访问量大,缓存过期瞬间并发穿过缓存层,直接访问持久层
指对某一个 key 的频繁访问,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就会直接请求数据库,就像在一个屏障上凿开了一个洞,例如微博由于某个热搜导致宕机。
其实就是:当某个 key 在过期的瞬间,有大量的请求并发访问,这类数据一段是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并回写缓存,导致数据库瞬间压力过大。
- 解决方案 × 2
设置热点数据永不过期
:从缓存层面上来说,不设置过期时间,就不会出现热点 key 过期后产生的问题。添加互斥锁
(★):使用分布式锁
,保证对每个 key 同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可,这种方式将高并发的压力转移到了分布式锁上,对分布式锁也是一种极大的考验。
3. 缓存雪崩
- 概念
缓存过期,大量并发导致缓存层+持久层宕机
指在某一个时间段,缓存集中过期失效或 Redis 宕机导致的,例如双十一抢购热门商品,这些商品都会放在缓存中,假设缓存时间为一个小时,一个小时之后,这些商品的缓存都过期了,访问压力瞬间都来到了数据库上,此时数据库会产生周期性的压力波峰,所有的请求都会到达存储层,存储层的调用量暴增,造成存储层挂掉的情况。
其实比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网,因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,此时的数据库还是可以顶住压力的,而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,有可能瞬间就把服务器压垮。
- 解决方案 × 3
配置 Redis 高可用
:其实就是搭建集群
环境,有更多的备用机。Redis 搭建集群参考:Redis 搭建集群步骤
限流降级
:在缓存失效后,通过加锁或者队列来控制读服务器以及写缓存的线程数量,比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
以参考日志级别设置预案:
(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
(2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
(3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
(4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。数据预热
:在项目正式部署之前,把可能用的数据预先访问一边,这样可以把一些数据加载到缓存中,在即将发生大并发访问之前手动触发加载缓存中不同的key,设置不同的过期时间,让缓存失效的时间尽量均衡。解决思路:
1、直接写个缓存刷新页面,上线时手工操作下;
2、数据量不大,可以在项目启动的时候自动进行加载;
3、定时刷新缓存。
4. 缓存倾斜
- 概念
某个缓存服务器压力过大而宕机
指某一台 redis 服务器压力过大而导致该服务器宕机。
- 解决方案
配置 Redis 高可用
:其实就是搭建集群
环境,有更多的备用机。Redis 搭建集群参考:Redis 搭建集群步骤
5. 淘汰机制
在 Redis 内存已经满的时候,添加了一个新的数据,执行淘汰机制。(redis.conf 中配置)
volatile-lru:在内存不足时,Redis 会再设置过了生存时间的key中干掉一个最近最少使用的key。
allkeys-lru:在内存不足时,Redis 会再全部的key中干掉一个最近最少使用的key。
volatile-lfu:在内存不足时,Redis 会再设置过了生存时间的key中干掉一个最近最少频次使用的key。
allkeys-lfu:在内存不足时,Redis 会再全部的key中干掉一个最近最少频次使用的key。
volatile-random:在内存不足时,Redis 会再设置过了生存时间的key中随机干掉一个。
allkeys-random:在内存不足时,Redis 会再全部的key中随机干掉一个。
volatile-ttl:在内存不足时,Redis 会再设置过了生存时间的key中干掉一个剩余生存时间最少的key。
noeviction:(默认)在内存不足时,直接报错。
淘汰方案:
- 指定淘汰机制的方式:maxmemory-policy 具体策略
- 设置Redis的最大内存:maxmemory 字节大小
redis 内存数据集大小上升到一定大小的时候,就会进行数据淘汰策略。
5.1 如何配置
通过配置 redis.conf 中的 maxmemory
这个值来开启内存淘汰功能
。
1 |
|
值得注意的是,maxmemory 为 0 的时候表示对 Redis 的内存使用没有限制。
根据应用场景,选择淘汰策略:
1 |
|
5.2 内存淘汰的过程
首先,客户端发起了需要申请更多内存的命令(如set)。
然后,Redis检查内存使用情况,如果已使用的内存大于maxmemory则开始根据用户配置的不同淘汰策略来淘汰内存(key),从而换取一定的内存。
最后,如果上面都没问题,则这个命令执行成功。
5.3 动态改配置命令
此外,redis 支持动态改配置,无需重启。
设置最大内存
1 |
|
设置淘汰策略
1 |
|
5.4 如何选择淘汰策略
allkeys-lru:如果应用对缓存的访问符合幂律分布,也就是存在相对热点数据,或者不太清楚应用的缓存访问分布状况,可以选择allkeys-lru策略。
allkeys-random:如果应用对于缓存key的访问概率相等,则可以使用这个策略。
volatile-ttl:这种策略使得可以向Redis提示哪些key更适合被eviction。
另外,volatile-lru策略和volatile-random策略适合将一个Redis实例既应用于缓存和又应用于持久化存储的时候,然而也可以通过使用两个Redis实例来达到相同的效果,值得一提的是将key设置过期时间实际上会消耗更多的内存,因此建议使用allkeys-lru策略从而更有效率的使用内存。
6. 生存时间到了删除?
key的生存时间到了,Redis 会立即删除吗?答:不会立即删除
。
定期删除:Redis每隔一段时间就去会去查看Redis设置了过期时间的key,会再100ms的间隔中默认查看3个key。
惰性删除:如果当你去查询一个已经过了生存时间的key时,Redis会先查看当前key的生存时间,是否已经到了,直接删除当前key,并且给用户返回一个空值。
总结:
定期删除:默认100ms查看3个过期的key,定期删除
惰性删除:查询时,redis检查是否过期,过期则删除key,返回空值
- 采用定期删除+惰性删除就没其他问题了么?
不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的
内存会越来越高
。那么就应该采用内存淘汰机制
。在redis.conf中有一行配置
1maxmemory-policy volatile-lru