前言

  从学校出来,做开发工作也有一定时间了,最近有想系统地进一步深入学习,但发现基础知识不够扎实,故此来回顾基础知识,进一步巩固、加深印象。

  最初开始接触编程时,总是自己跌跌撞撞、不断摸索地去学习,再一点点应用到实际项目中,知识点才更加清晰。后来,尝试写博客,把学到的知识试着分享出来,也是一次巩固的过程。

1、问:Redis雪崩了解吗?

  答:我了解的。目前电商首页以及热点数据都会去做缓存,一般缓存都是定时任务去刷新,或者是查不到之后去更新,定时任务刷新就有一个问题。

  举个简单例子:如果所有首页的key失效时间都是12小时,中午12点刷新,我0点开始抢单活动,大量用户涌入,假设当时每秒6000个请求,本来缓存可以扛住每秒5000个请求,但是缓存当时所有的 Key 都失效了。此时,1秒 6000个请求全部打到数据库,数据库必然扛不住,它会报一下警,但实际情况可能是DBA都没反应过来数据库就直接挂了。此时,如果没有什么特别的方案来处理这个故障,DBA很着急,重启数据库,但是数据库马上又被新的流量打死了。这就是我理解的缓存雪崩。

  同一时间缓存大面积失效,那一瞬间Redis跟没有一样,这个数量界别的请求直接打到数据库几乎是灾难性的。试想一下,如果打挂的是一个用户服务的库,那其他依赖它的库所有的接口几乎都会报错,如果没有做熔断等策略,基本上就是一瞬间挂一片的节奏,任你怎么重启用户都会把你打挂,等你重启好的时候,用户早睡觉去了,并且对我们的产品失去了信息。

2、问:那遇到这种情况,你是怎么去应对的?

  答:处理缓存雪崩相对简单,在批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好,这样就可以保证数据不会在同一时间大面积失效,我相信,Redis这点流量还是顶得住的。

setRedis( Key, value, time+Math.random() * 1000 );
  如果Redis是集群部署,将热点数据均匀分布在不同的Redis库中也能避免全部失效问题,不过在生产环境中操作集群的时候,单个服务都是对应的单个Redis分片,是为了方便数据的管理,但是也同样有可能回失效这样的弊端,失效时间随机是个好策略。

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

3、问:那你了解缓存穿透和击穿吗?跟雪崩有什么区别?

  答:有了解的。先说说缓存穿透吧。缓存穿透是指缓存和数据库中都没有数据,而用户不断发起请求,我们数据库的 id 都是1开始自增上去的,如果发起 id = -1 的数据 或 id 为特别大(不存在)的数据,这是用户就很可能是攻击者,攻击会导致数据库压力过大,严重会击垮数据库。

  小点的单机系统,可能用 postman 就能搞死,比如自己买的云服务。


  像这种没有对参数进行校验,数据库 ID 都是大于0的,我们一直用小于 0 的参数去请求,每次都能绕开Redis直接打到数据库,数据库也查不到,每次都这样,并发高点就容易崩掉了。

  至于缓存击穿嘛,这个和缓存雪崩有点像,但是又有点区别,缓存雪崩是因为大面积的缓存失效,打崩了DB,而缓存击穿不同的是缓存击穿是指一个Key非常热点,在不停地扛着大并发,大并发集中对一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿透缓存,直接请求数据库,就像一个完好无损的桶上凿开了一个洞。比如系统中某个商品,突然变成爆款,大量用户访问该商品,就可能发生缓存击穿。

4、问:雪崩、穿透、击穿你们都是怎么解决的?

  答:缓存穿透是请求绕过缓存直接查数据库,我会在接口层增加校验,比如对用户权限校验,接口参数做校验,不合法的参数直接Return,比如 id 做基础校验,id<=0的直接拦截等等。

  (这里我想补充一点,就是我们在开发的时候,要保持一颗“不信任”的心,就是不要相信任何调用方,比如我提供API接口出去,我需要这几个入参,那作为被调用方,任何可能的参数情况都应该被考虑到、并做校验,因为我不相信调用我接口的人,我不知道他会传什么参数给我。

  举个简单例子,我这个接口是分页查询的,但是我没有对分页参数的大小做限制,调用的人万一 一口气查 Interger.MAX_VALUE  一次请求就需要我几秒,多几个并发我接口就挂了。是公司同事调用还好,大不了发现了改掉就是了,但是如果是黑客或者竞争对手,那问题就大了。在双十一或者抢单活动的时候调用这个接口,那损失就大了。)

  从缓存取不到的数据,在数据库中也没有取到(不管是数据不存在,还是系统故障),这时也可以将对应Key的Value对写为null、“位置错误”、“稍后重试”这样的值(具体和产品沟通),或者看具体的场景,缓存的时间可以设置短点,如30秒(设置太长会导致正常情况下也没法适用),这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴

  这样可以防止攻击用户反复用同一个 id 暴力攻击,但是我们要知道正常用户是不会在单秒内发起这么多次请求的,那网关层Nginx 我们也可以添加配置项,可以让运维人员对单个 IP 每秒访问次数超出阀值的 IP 都拉黑。

5、问:还有其他办法吗?

  答:记得Redis还有一个高级用法布隆过滤器(Bloom Filter),这个也能很好地防止缓存穿透的发生,他的原理也很简单,就是利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在,不存在你Return就好了,存在你就去查了DB刷新Key、Value,再Return。

  缓存击穿的话,设置热点数据永远不过期,或者加上互斥锁就能搞定了。(代码如下)

 /**
* 获取数据
* @param key 查询参数
* @return data 数据
* @throws InterruptedException 异常
* @author 柠檬先生
*/
public static String getData(String key) throws InterruptedException {
// 从redis查询数据
String result = getDataFromKV(key);
// 校验数据
if (StringUtils.isBlank(result)){
// 获取锁
if (reenLock.tryLock()) {
// 去数据库查询
result = getDataFromDB(key);
// 校验
if (StringUtils.isNotBlank(result)){
// 保存到缓存中;
setDataToKV(key, result);
}
// *释放锁 正常会再finally里面释放;
reenLock.unLock();
}else {
// 睡一会儿再拿
Thread.sleep(100L);
result = getData(key);
}
}
return result;
}
//这里的锁是单机版玩法.,分布式锁还是得靠lua脚本这样的;

小结:

  以上介绍了Redis的雪崩、击穿、穿透,三者其实都差不多,但是又有些区别,在面试中这是问到缓存必问的,因为缓存的雪崩、击穿、穿透,是缓存最大的问题,要么不出现,一旦出现就是致命性的问题,所以面试官一定会问。

  我们学习的时候一定要理解是怎么发生,以及怎么去避免的,发生之后怎么去抢救,你可以不是了解很深入,但是你一定不能什么都不去想,面试有时候不一定是对只是面的拷问,或许是对你态度的拷问,如果你思路清晰,然后知其然还能知其所以然那就更棒了,还能知道怎么预防,那offer就是手到擒来了。

总结:

  一般避免以上情况发生我们要从3个时间段去分析:

  • 事前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃;
  • 事中:本地 ehcache 缓存 + Hystrix 限流 + 降级,避免 **MySQL** 被打死。
  • 事后:Redis 持久化 RDB + AOF,一旦重启,自动从磁盘上加载数据,快速回复缓存数据。

  上面提到的限流组件,可以设置每秒的请求,有多少能通过组件,剩余未通过的请求,怎么办?走降级!可以返回一些默认值,或者友情提示,或者空白的值。

  这样做的好处是:数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。只要数据库不死,对用户来说, 3/5 的请求都是可以被处理的。只要有 3/5 的请求可以被处理,就意味着系统没有死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次就可以刷出来一次。

  这个在目前主流的互联网大厂里面是最常见的,以前遇到过,某明星爆出什么事情,会发现微博怎么刷都是空白界面,但是有的人有直接进去了,多刷几次又出来了,这就是做了降级,牺牲部分用户体验来换取服务器的安全,还行。

  还有就是今年(2019)的双十一,走的就是“限流、降级”,下单接口其实没有挂,牺牲部分用户体验,保住服务器,你多点几下是可以成功的,等流量高峰过去了,所有的用户全部都恢复正常访问,服务器啥事也没有。

  去年(2018)退款接口被打崩了,今年阿里也聪明了很多,退款接口在12日0点开放,这就有效避开了流量高峰。

Redis系列三 - 缓存雪崩、击穿、穿透的更多相关文章

  1. (转)面试前必知Redis面试题—缓存雪崩+穿透+缓存与数据库双写一致问题

    背景:redis问题在面试过程中经常被问到,对于常见问题一定不能放过. 面试前必知Redis面试题—缓存雪崩+穿透+缓存与数据库双写一致问题 一.缓存雪崩 1.1什么是缓存雪崩? 如果缓存数据设置的过 ...

  2. Redis 面试常见问题———缓存雪崩、缓存击穿以及缓存穿透

    在开发中会面临缓存异常可能会出现三个问题,分别是缓存雪崩.缓存击穿和缓存穿透.这三个问题会导致大量请求从缓存转移到数据库,如果请求的并发量很大的话,就会导致数据库崩溃.所以在面试官也会经常问这些问题. ...

  3. redis缓存雪崩、穿透、击穿概念及解决办法

    缓存雪崩 对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机.缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据 ...

  4. Redis系列(八)--缓存穿透、雪崩、更新策略

    1.缓存更新策略 1.LRU/LFU/FIFO算法剔除:例如maxmemory-policy 2.超时剔除,过期时间expire,对于一些用户可以容忍延时更新的数据,例如文章简介内容改了几个字 3.主 ...

  5. Redis 缓存雪崩、穿透、击穿

    缓存雪崩 定义: 同一时间所有 key 大面积失效,比如网站首页的数据基本上都是同一批次去缓存的. 解决方法: ① 存的时候设定随机的失效时间. ② 服务做熔断处理(异常或着慢查询 Hystrix 限 ...

  6. 面试前必知Redis面试题—缓存雪崩+穿透+缓存与数据库双写一致问题

    今天来分享一下Redis几道常见的面试题: 如何解决缓存雪崩? 如何解决缓存穿透? 如何保证缓存与数据库双写时一致的问题? 一.缓存雪崩 1.1什么是缓存雪崩? 回顾一下我们为什么要用缓存(Redis ...

  7. 缓存雪崩、穿透如何解决,如何确保Redis只缓存热点数据?

    缓存雪崩如何解决? 缓存穿透如何解决? 如何确保Redis缓存的都是热点数据? 如何更新缓存数据? 如何处理请求倾斜? 实际业务场景下,如何选择缓存数据结构 缓存雪崩 缓存雪崩简单说就是所有请求都从缓 ...

  8. 关于redis的几件小事(七)redis缓存雪崩与穿透

    1.缓存雪崩 (1)什么是缓存雪崩 缓存雪崩指的是在同一时刻,缓存大量失效,导致大量的请求直接到了数据库,数据库压力剧增,引起系统崩溃.可能出现的情况有: ①大量的key设置了相同的过期时间,导致在缓 ...

  9. Redis中的缓存雪崩与缓存穿透

    1.缓存雪崩 1.1什么是缓存雪崩? 如果我们的缓存挂掉了,这意味着我们的全部请求都跑去数据库了. 我们都知道Redis不可能把所有的数据都缓存起来(内存昂贵且有限),所以Redis需要对数据设置过期 ...

随机推荐

  1. maven-assembly-plugin 打包包含多余依赖问题一则

    有同事反馈自己maven-assembly-plugin打的包里面多了很多mvn dependency:tree中没有的jar. 我当时只是试着把他的maven-assembly-plugin更新到了 ...

  2. ORs-5-OR Subgenomes Variation among Birds, Sea Turtle and Alligator

    OR Subgenomes Variation among Birds, Sea Turtle and Alligator 由 该图数据计算每种鸟的relative percentage,得到下图: ...

  3. Python opencv PIL numpy base64互相转化

    PIL2numpy and numpy2PIL from PIL import Image import numpy image = Image.open('timg.jpeg')# image is ...

  4. Tarjan相关

    先码住: 板子:http://www.cnblogs.com/luckycode/p/5255656.html 求割点/割边:http://www.cnblogs.com/c1299401227/p/ ...

  5. HTTP-web服务器接收到client请求后的处理过程(很详细)

    1. 客户发起情况到服务器网卡: 2. 服务器网卡接受到请求后转交给内核处理: 3. 内核根据请求对应的套接字,将请求交给工作在用户空间的Web服务器进程 4. Web服务器进程根据用户请求,向内核进 ...

  6. Synchronized的jvm实现

    参考文档: https://www.cnblogs.com/dennyzhangdd/p/6734638.html

  7. unittest(9)- 使用ddt给测试用例传参

    # 1. http_request.py import requests class HttpRequest: def http_request(self, url, method, data=Non ...

  8. 吴裕雄--天生自然KITTEN编程:滂沱大雨

  9. C#利用反射调用PB编译的COM组件

    问题: 1.根据COM组件的ProgID,得到COM组件公开的类型 2.创建COM组件提供的类型的对象 3.调用执行方法 正确姿势 C#利用反射调用(后期绑定)PB编译的COM组件 C#调用COM组件 ...

  10. 本地开启https服务

    ### ##自签名证书 ##配置Apache服务器SSL ##自己作为CA签发证书 ###这里是OpenSSL和HTTPS的介绍 OpenSSL HTTPS 开启HTTPS配置前提是已在Mac上搭建A ...