决战圣地玛丽乔亚Day49---

发布时间 2023-04-07 07:22:45作者: EmiXXXt

缓存雪崩/穿透/击穿是什么?如何处理,代码实现逻辑?

缓存雪崩:redis中大量key集体过期

缓存穿透:大量请求根本不存在的key

缓存击穿:redis中一个热点key过期(大量用户访问该热点key,但是热点key过期)

缓存雪崩:

  • 进行预先的热门词汇的设置,进行key时长的调整
  • 实时调整,监控哪些数据是热门数据,实时的调整key的过期时长
  • 使用锁机制

 大量的key集体过期,本质是他们的失效时间一致。

1)将失效时间分散开:
通过使用自动生成随机数使得key的过期时间是随机的,防止集体过期
2)使用多级架构:
使用nginx缓存+redis缓存+其他缓存,不同层使用不同的缓存,可靠性更强
3)设置缓存标记:
记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去跟新实际的key
4)使用锁或者队列的方式:
如果查不到就加上排它锁,其他请求只能进行等待

缓存穿透:

缓存穿透很有可能是黑客攻击所为,黑客通过发送大量的高并发的无法响应的请求给服务器,由于请求的资源根本就不存在,Reids中找不到就去DB,DB就很容易被打垮了

解决方式:

1)实时监控: 对redis进行实时监控,当发现redis中的命中率下降的时候进行原因的排查,配合运维人员对访问对象和访问数据进行分析查询,从而进行黑名单的设置限制服务(拒绝黑客攻击)

2)布隆过滤器:布隆过滤器很擅长处理这种判断是否存在的问题,使用BitMap作为布隆过滤器,将目前所有可以访问到的资源通过简单的映射关系放入到布隆过滤器中(哈希计算),当一个请求来临的时候先进行布隆过滤器的判断,如果有那么才进行放行,否则就直接拦截

布隆过滤器的原理:

- bf.add 添加一个
- bf.madd 添加多个
- bf.exists 查询是否在过滤器中
- bf.mexists 查询多个是否在过滤器中
-bf.reserve自定义布隆过滤器:
- key:键
- error_rate:期望错误率,期望错误率越低,需要的空间就越大 默认0.01
- capacity:初始容量,当实际元素的数量超过这个初始化容量时,误判率上升 默认100
- 布隆过滤器的error_rate越小,需要的存储空间就越大,对于不需要过于精确的场景,error_rate设置稍大一点也可以。布隆过滤器的capacity设置的过大,会浪费存储空间,设置的过小,就会影响准确率,所以在使用之前一定要尽可能地精确估计好元素数量,还需要加上一定的冗余空间以避免实际元素可能会意外高出设置值很多。总之,error_rate和 capacity都需要设置一个合适的数值。

通过多个hash函数把一个值分散到不同位置上. 例如 dwj这个值通过三个不同的hash函数,分在bit数组的不同位置上,位置上标记1。 下次查找dwj是否存在,看到三个位置都是1说明可能存在,因为这个位置上的1也可能是别的值映射上去的。利用布隆数组可以非常快速的查找是否存在,不存在就是不存在,存在是可能存在。 对布隆数组的数组长度和函数个数有一定的要求,根据具体的数据来设置可以精确布隆数组的计算精准度。 容量越大,落点就越不可能重合,精准度会高但是会造成内存的损耗大。函数越少,误判率就会越高。
大,落点就越不可能重合,精准度会高但是会造成内存的损耗大。函数越少,误判率就会越高。

 

 

 

3)接口校验:类似于用户权限的拦截,对于id=-3872这些无效访问就直接拦截,不允许这些请求到达Redis、DB上。

 

缓存击穿:

redis中的某个热点key过期,但是此时有大量的用户访问该过期key

类似于“某男明星塌房事件”上了热搜,这时候大量的“粉丝”都在访问该热点事件,但是可能优于某种原因,redis的这个热点key过期了,那么这时候大量高并发对于该key的请求就得不到redis的响应,那么就会将请求直接打在DB服务器上,导致整个DB瘫痪。

1)提前对热点数据进行设置

类似于新闻、某博等软件都需要对热点数据进行预先设置在redis中

2)监控数据,适时调整

监控哪些数据是热门数据,实时的调整key的过期时长

3)使用锁机制

只有一个请求可以获取到互斥锁,然后到DB中将数据查询并返回到Redis,之后所有请求就可以从Redis中得到响应

 

扩容数据迁移问题?哈希一致性

某台Redis宕机后,如何迁移存储的键值对转移到集群中的别的节点?

1)直接取模

为了避免大量数据放在单表单库,可以在存数据的时候通过一定的取模方式,把数据放在不同的数据库达到分库分表的效果。

但是这么做的缺点是:如果redis的数量变化,需要重新对老数据进行计算。如果不对老数据计算就会造成某库某表的数据过多的问题。要是重新对老数据进行计算,就要阻塞造成不小开销。

2)一致性hash

一致性哈希算法是通过将哈希空间分为1-2^32-1的圆环,我们把cache节点和我们的object对象数据hash运算以后分别映射到圆环上,然后会把对象的数据按照顺时针绑定到最近的一个cache节点,实现数据与节点的绑定,这样做的好处就是如果节点发生增删,都只影响临近的那个数据变更,可以最大程度上抑制了键的重新分布(生产中也就是指服务器缓存的重新分布)。
当然一致性哈希会存在哈希倾斜性的问题,可能会导致数据倾斜和缓存雪崩,我们可以引入虚拟节点,让虚拟节点哈希后得到真实节点,来解决这个问题。

 

 

通过这种方式,我们可以把移出/添加节点对于键值对数据的影响降到最低,影响的范围仅仅是节点之间存在的距离。

数据倾斜问题:

理想状态下是节点均匀分布在圆环上,但是实际有时候并非如此。

 

例如,当A退出时,它原本所负责的缓存将全部交给B处理。这就意味着B的访问压力会瞬间增大。设想一下,如果B因为压力过大而崩溃,那么更大的压力又会向C压过去,最终服务压力就像滚雪球一样越滚越大,最终导致雪崩。

为了使对象尽可能均匀地映射到所有的缓存实例中(解决缓存实例分布不均匀的问题),引入虚拟节点的概念。虚拟节点其实为真实节点在hash空间中的复制品,一个真实节点可以对应多个虚拟节点。虚拟节点的hash求值可以在真实节点的求值基础上加入编号等信息 hash(realCacheKey#1) 、 hash(realCacheKey#2)。

 通过虚拟节点的原理我们来解决hash倾斜性的问题:

 

虚拟节点就是把原来的节点A,B,C再次进行hash成多个节点分布在hash环上。

 

虚拟节点的hash倾斜性如何保证:

hash算法保证,均匀分布式概率上的均匀,虚拟节点足够的时候就能够保证大概的均匀。

 

缓存命中率及单一热点问题:
一致性哈希解决的是某节点宕机后缓存失效的问题,只会导致相邻节点负载增加。但是因为宕机后需要重新从数据库读取,会导致此时缓存命中率下降及db压力增加。
也无法避免单一热点问题。某一数据被海量请求,不论怎么哈希,哈希环多大,数据只存在一个节点,早晚有被打垮的时候。

此时的解决策略是每个节点主备或主主集群。


hash漂移:
某个节点失效了,缓存都漂到下个节点了;然后一会它又恢复了,这时候它就有脏数据了。
解决办法一是每个节点引入集群。
不用集群想彻底解决这个问题,可能需要引入第三方健康检查组件,如Consul,发现节点不稳定立即删除下线。

 

 

一致性hash算法最大程度上抑制了键的重新分布(服务器缓存的重新分布),而且我们还可以采用虚拟节点的思想,比如我们每个实际节点都配置100-500个虚拟节点,这样就能抑制分布不均匀。

命中率公式:n是服务器台数,m是增加的服务器,因此随着分布式集群不断扩大,命中率会越来越高。

(1-n/(n+m))*100%