Redis的雪崩、击穿、穿透

Redis的雪崩、击穿、穿透

      Redis的雪崩,击穿,穿透,三者其实都差不多,但是又有一些区别。它们是缓存最大的问题,要么不出现,一旦出现就是致命性的问题。

     一、缓存穿透(Cache Penetration)

      1.  什么是缓存穿透

      请求去查询一条在数据库中根本就不存在的数据,也就是缓存和数据库中都查询不到这条数据,但是请求每次都会打到数据库上面去。这种查询不存在数据的现象我们称为缓存穿透

      2. 缓存穿透带来的问题

      如果有黑客对系统进行攻击,拿一个不存在的id 去查询数据(比如数据库中主键ID 是1开始自增上去的,而用户查询的id值为 -1 的数据或 id 为特别大不存在的数据),会产生大量的请求绕开了缓存直接到数据库去查询。可能会导致数据库由于压力过大而宕掉。小点的单机系统,基本上用postman就能搞挂,比如个人买的阿里云服务。

      如下图:

       

     3. 解决办法

     1) 接口层增加校验

     在接口层增加校验,比如用户鉴权校验,参数做校验,不合法的参数直接代码Return,比如:id 做基础校验,id <=0的直接拦截等

     注意:开发程序的时候不要相信前端或者调用方那边传递过来的参数。作为被调用方,任何可能的参数情况都应该被考虑到,做校验。因为你不知道调用方会传什么参数给你。

     比如:某个接口是分页查询的,但是作为接口提供方,没对分页参数的大小做限制,调用的人万一 一口气查出所用的数据。 一次请求就要几秒,多几个并发服务可能就挂掉了。

     2)缓存空值

     之所以会发生穿透,就是因为缓存中没有存储这些空数据的key。从而导致每次查询都到数据库去了。

     那么我们就可以为这些key对应的值设置为null 丢到缓存里面去。后面再出现查询这个key 的请求的时候,直接返回null 。缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。

     这样可以防止攻击用户反复用同一个id暴力攻击

     3)布隆过滤器(Bloom Filter)

     BloomFilter 类似于一个hbase set 用来判断某个元素(key)是否存在于某个集合中。这种方式在大数据场景应用比较多,比如 Hbase 中使用它去判断数据是否在磁盘上。还有在爬虫场景判断url 是否已经被爬取过。这种方案可以加在缓存空值前,在缓存之前在加一层 BloomFilter ,在查询的时候先去 BloomFilter 去查询 key 是否存在,如果不存在就直接返回,存在再走查缓存 -> 查 DB。

    关于布隆过滤器的详情原理,请参考文章《布隆过滤器(Bloom Filter)》

    4.  Nginx配置

    正常用户是不会在单秒内发起这么多次请求的,那网关层Nginx中有配置项,可以对单个IP每秒访问次数超出阈值的IP都拉黑

    Nginx按请求速率限速模块使用的是漏桶算法,即能够强行保证请求的实时处理速度不会超过设置的阈值。

    Nginx官方版本限制IP的连接和并发分别有两个模块:

    1)limit_req_zone 用来限制单位时间内的请求数,即速率限制采用的漏桶算法 "leaky bucket"。

    例如: 限制只允许一分钟内只允许一个ip访问60次配置,也就是平均每秒1次

    limit_req_zone $binary_remote_addr zone=allips:10m rate=60r/m; 

    2)limit_req_conn 用来限制同一时间连接数,即并发限制

    例如:limit_req_conn=allips;

    3)如何选择

   首先接口层增加校验这是最基本的,nginx配置也是需要的,那么如何根据不同的情况,采用布隆过滤器还是缓存空值呢?

  • 针对于一些恶意攻击,攻击带过来的大量key 是不存在的。像这种key异常多、请求重复率比较低的数据,我们就没有必要进行缓存,使用布隆过滤器直接过滤掉。
  • 而对于空数据的key有限的,重复率比较高的,我们则可以采用缓存空值的方式来处理。(缓存空值其实是一种用空间换时间的策略)

    二、缓存击穿(Hotspot Invalid)

    1. 什么是缓存击穿

    在平常高并发的系统中,大量的请求同时查询一个 key(热点) 时,当这个Key在失效的瞬间,就会导致大量的请求都打到数据库上面去。这种现象我们称为缓存击穿。

    当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。

    2. 缓存击穿带来的问题

    会造成某一时刻数据库请求量过大,压力剧增。

    3. 解决办法

    1)设置热点key不过期

    如果缓存数据几乎不会变化:设置热点数据永远不过期

    2)加上互斥锁(mutex key)

    如果缓存数据更新不频繁,那么可以使用分布式互斥锁,在缓存失效的时候,只允许少量请求访问到数据库,并重新构建缓存。

    使用分布式锁,保证对于每个 key 同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。详情请参考文章《Redis实现分布式锁》

    实现过程:在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。

    3)定时重新构建缓存

    如果缓存数据更新频繁或者构建困难,那么就可以使用定时任务定时重新构建缓存,也能保证热点数据永不失效。

   三、缓存雪崩(Cache Avalanche)

   1.  什么是缓存雪崩

   当某一时刻发生大规模的缓存失效的情况,比如你的缓存服务宕机了,会有大量的请求进来直接打到DB上面。结果就是DB 撑不住,挂掉。这种现象我们称为缓存雪崩。

   2. 缓存雪崩带来的问题

     同一时间大面积失效,那一瞬间Redis跟没有一样,那这个数量级别的请求直接打到数据库几乎是灾难性的,如果挂掉的是一个用户服务的库,那其他依赖他的库所有的接口几乎都会报错,如果没做熔断等策略基本上就是瞬间挂一片的节奏。无论如何重启,用户的大量请求都会让这个库挂掉。

     3. 应对方案

    分为事前、事中、事后3种不同情况的应对方案

    3.1  事前

    1)使用集群缓存,保证缓存服务的高可用

    这种方案就是在发生雪崩前对缓存集群实现高可用,如果是使用 Redis,可以使用 主从+哨兵 ,Redis Cluster 来避免 Redis 全盘崩溃的情况。

    2)设置随机失效时间

    在批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会在同一时间大面积失效

    setRedis(Key,value,time + Math.random() * 10000)

    3)设置热点数据永远不过期

    设置热点数据永远不过期,有更新操作就更新缓存就好了(比如运维更新了首页商品,那你刷下缓存就完事了,不要设置过期时间),电商首页的数据也可以用这个操作

    4) 均匀分布

   如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中

    3.2 事中

    ehcache本地缓存 + Hystrix限流&降级,避免MySQL被打挂

    使用 ehcache 本地缓存的目的也是考虑在 Redis Cluster 完全不可用的时候,ehcache 本地缓存还能够支撑一阵。

    使用 Hystrix进行限流 & 降级 ,比如一秒来了5000个请求,我们可以设置假设只能有一秒 2000个请求能通过这个组件,那么其他剩余的 3000 请求就会走限流逻辑。然后去调用我们自己开发的降级组件(降级),比如设置的一些默认值呀之类的。以此来保护最后的 MySQL 不会被大量的请求给打死。

    3.3 事后

    开启Redis持久化机制,尽快恢复缓存集群。

    Redis 持久化 RDB + AOF,一旦重启,就能从磁盘上自动加载数据恢复内存中的数据。

    四、缓存击穿与缓存雪崩的区别

    缓存击穿跟缓存雪崩有点像,但是又有一点不一样,缓存雪崩是因为大面积的缓存失效,打崩了DB,而缓存击穿不同的是缓存击穿是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问。

 

   参考链接:https://segmentfault.com/a/1190000022029639

posted @ 2020-12-08 15:31  欢乐豆123  阅读(380)  评论(0编辑  收藏  举报