redis的6大解决方案

如何解决Redis雪崩、穿透、并发等5大难题

缓存雪崩

那么,当大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是缓存雪崩的问题。

比如一个雪崩的简单过程:

1、redis集群大面积故障

2、缓存失效,但依然大量请求访问缓存服务redis

3、redis大量失效后,大量请求转向到mysql数据库

4、mysql的调用量暴增,很快就扛不住了,甚至直接宕机

5、由于大量的应用服务依赖mysql和redis的服务,这个时候很快会演变成各服务器集群的雪崩,最后网站彻底崩溃。


如何预防缓存雪崩:

可以看到,发生缓存雪崩有两个原因:

1. 大量数据同时过期;
2. Redis 故障宕机;

不同的诱因,应对的策略也会不同。

大量数据同时过期

1. 均匀设置过期时间

如果要给缓存数据设置过期时间,应该避免将大量的数据设置成同一个过期时间。我们可以在对缓存数据设置过期时间时, 给这些数据的过期时间加上一个随机数,这样就保证数据不会在同一时间过期。
2. 互斥锁

当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完成后,再释放锁。未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。

实现互斥锁的时候,最好设置超时时间,不然第一个请求拿到了锁,然后这个请求发生了某种意外而一直阻塞,一直不释放锁,这时其他请求也一直拿不到锁,整个系统就会出现无响应的现象。


3. 后台更新缓存

业务线程不再负责更新缓存,缓存也不设置有效期,而是让缓存“永久有效”,并将更新缓存的工作交由后台线程定时更新。

事实上,缓存数据不设置有效期,并不是意味着数据一直能在内存里,因为当系统内存紧张的时候,有些缓存数据会被“淘汰”,而在缓存被“淘汰”到下一次后台定时更新缓存的这段时间内,业务线程读取缓存失败就返回空值,业务的视角就以为是数据丢失了。

解决上面的问题的方式有两种。

第一种方式,后台线程不仅负责定时更新缓存,而且也负责频繁地检测缓存是否有效,检测到缓存失效了,原因可能是系统紧张而被淘汰的,于是就要马上从数据库读取数据,并更新到缓存。

这种方式的检测时间间隔不能太长,太长也导致用户获取的数据是一个空值而不是真正的数据,所以检测的间隔最好是毫秒级的,但是总归是有个间隔时间,用户体验一般。

第二种方式,在业务线程发现缓存数据失效后(缓存数据被淘汰),通过消息队列发送一条消息通知后台线程更新缓存,后台线程收到消息后,在更新缓存前可以判断缓存是否存在,存在就不执行更新缓存操作;不存在就读取数据库数据,并将数据加载到缓存。这种方式相比第一种方式缓存的更新会更及时,用户体验也比较好。

在业务刚上线的时候,我们最好提前把数据缓起来,而不是等待用户访问才来触发缓存构建,这就是所谓的缓存预热,后台更新缓存的机制刚好也适合干这个事情。

Redis 故障宕机

针对 Redis 故障宕机而引发的缓存雪崩问题,常见的应对方法有下面这几种:

1. 服务熔断或请求限流机制;
2. 构建 Redis 缓存高可靠集群;

  1. 服务熔断或请求限流机制

因为 Redis 故障宕机而导致缓存雪崩问题时,我们可以启动服务熔断机制暂停业务应用对缓存服务的访问,直接返回错误,不用再继续访问数据库,从而降低对数据库的访问压力,保证数据库系统的正常运行,然后等到 Redis 恢复正常后,再允许业务应用访问缓存服务。

服务熔断机制是保护数据库的正常允许,但是暂停了业务应用访问缓存服系统,全部业务都无法正常工作。

为了减少对业务的影响,我们可以启用请求限流机制只将少部分请求发送到数据库进行处理,再多的请求就在入口直接拒绝服务,等到 Redis 恢复正常并把缓存预热完后,再解除请求限流的机制。

缓存击穿

我们的业务通常会有几个数据会被频繁地访问,比如秒杀活动,这类被频地访问的数据被称为热点数据。

如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。

可以发现缓存击穿跟缓存雪崩很相似,你可以认为缓存击穿是缓存雪崩的一个子集。

应对缓存击穿可以采取前面说到两种方案:

  • 互斥锁方案:保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
  • 后台更新缓存:不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间;

缓存穿透

缓存穿透是指查询一个数据库不存在的数据。例如:从缓存redis没有命中,需要从mysql数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
正常接口查询肯定查询的是数据库存在的数据,如果数据库不存在,只能说明俩种可能,第一种自身业务出现问题,第二种恶意攻击。

解决方法有俩个:

  • 返回空对象

如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库。设置一个过期时间或者当有值的时候将缓存中的值替换掉即可。

  • 布隆过滤器拦截

查看相关布隆过滤器相关只是可查看我的文章

在访问缓存层和存储层之前,将所有存在的key用布隆过滤器先存储起来,做第一层拦截

比如有个用户下单接口,该接口传递参数userid,然后使用userid为key查询缓存进行下单操作。

那么该接口就可以使用布隆过滤器来过滤userid,我们首先将系统的所有userid都加入到布隆过滤器中,当请求的userid使用布隆过滤器过滤之后发现不存在,那么直接返回客户端即可,并不需要查询缓存层和数据层了;如果布隆过滤器过滤之后发现useid可能存在(布隆控制器存在误判情况,只能判断可能存在,而不能断定一定存在)的话就可以继续执行流程:先读取缓存,如果缓存存在直接返回客户端,如果不存在,则查询后端存储,如果后端存储查询到了数据就写入缓存,最后返回客户端,由于布隆过滤器存在误判情况,所以如果后端存储查询不到了数据,则结合第一种方式返回给客户端空对象,并写入缓存

总结:
由于布隆过滤器存在误判情况,所以使用过滤器方式和返回空对象方式必须结合使用。
排除自身业务的问题,假设遇到恶意攻击,传递的userid都不是真实的,也就是数据库中不存在的。针对这俩种结合的方案分析如下,如果第一层布隆过滤器判断不存在,则直接在缓存层之前就给过滤了,就不会到达缓存层和数据层了,可以大大减少压力
如果布隆过滤器认为可能存在后,那么就会到达缓存层和数据层了,由此可见使用布隆过滤器方案的必要性。

缓存并发

其实redis自身就是单线程操作,redis本身并没有锁的概念,按照先到先执行的原则,先到的先执行,其余的阻塞。但是利用predis phpredis等客户端对Redis进行并发访问时会出现问题。典型的例子就是库存超卖,解决方案有以下俩种

  • 这里可以使用redis的分布式锁可以解决并发问题。如命令set k v px ms nx,该命令在k不存在时才赋值k。也就是说如果返回true,则代表获取锁成功,如果返回false则代表已有资源获取锁,此时需要轮训,处于阻塞状态。
  • 可以将redis操作放在队列中使其串行化,必须的一个一个执行,如果放到队列进行串行化的话,效率会急剧下降。

缓存预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。

这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

解决思路:

1、直接写个缓存刷新页面,上线时手工操作下;

2、数据量不大,可以在项目启动的时候自动进行加载;

目的就是在系统上线前,将数据加载到缓存中。

以上就是缓存雪崩、预热、降级等的介绍。

缓存热点

  1. 加节点
    有些热点数据访问量会特别大,单个缓存节点(例如Redis)无法支撑这么大的访问量。如果是读请求访问量大,可以考虑读写分离,一主多从的方案,用从节点分摊读流量;如果是写请求访问量大,可以采用集群分片方案,用分片分摊写流量。以秒杀扣减库存为例,假如秒杀库存是100,可以分成5片,每片存20个库存。

  2. 拆分复杂结构
    比如当前key是一个hash结构,可以将该key分解为多个string类型,让其分布在不同节点,进行降低压力

  3. 迁移热点key
    以redis cluster为例,可以新增一台全新的节点,将热点key的slot迁移到该节点上,该节点的cpu,内存,io都需要好点。

  4. 本地缓存加发布订阅机制

    要解决单点存在hot key的问题,就通过多机分流来解决,而本地缓存就是一种分流方案。由本地缓存来分担流量,这样即使有热点key存在,有多少业务系统进程就有多少相互独立的缓存来分担流量,可以很好的解决热点key的问题。

    本地缓存解决了热点,但同时也带来了数据一致性的问题,在短时间内同一客户端访问业务系统可能会取到不一致的缓存结果,通常可使用redis的发布订阅功能来通知所有本地缓存。

  5. 降级服务
    如果节点读压力过大,我们可以关闭一些不重要的读方面的服务,以保证核心业务的正常运行,必须可以关闭商品查看评论功能或者关闭查看物流信息功能。
    如果节点写压力过大,我们可以关闭一些次要功能,累死用户商品评价功能

  6. 熔断机制
    如果redis服务阻塞,将会阻塞业务线程,为了避免消耗完线程池内的所有的线程,我们需要有熔断机制, 熔断机制的本质就是fail fast,如果在一定内时间没有返回数据,我们可以触发熔断机制,触发回调函数,回调函数内用户可以自己定义,上面介绍的使用本地缓存方案就是一种熔断机制。

热点key重建优化

开发人员使用“缓存+过期时间”的策略即可以加速数据读写,又保证了数据的定时更新,这种模式基本可以满足绝大部分需求,但是如果3个条件同时出现,可能会对系统造成致命的危害

  • 当前key是热点key,并发量非常大
  • 当前key缓存正好失效
  • 重建缓存不能在短期时间内完成,可能是一个复杂计算

正常业务流程为,先读取缓存,如果缓存存在直接返回客户端,如果不存在,则查询后端存储并进行计算,然后写入缓存,最后返回客户端,伪代码如下

//先读取缓存
value=redis.get(key)
if(value == null){
    // 读取数据库等系列操作,并计算,获得变量v
    value=计算获得value
    redis.set(key,value)
}
....
处理业务
.....
return value

比如此代码并发很高,就出现如下问题:
如果第一个线程还有执行完redis的set操作时,第二个线程在执行到get操作时,返现value为null,所以第二个线程也会执行redis的set操作,如果并发很高,可能第三个,第四个。。。。线程都会执行redis的set操作,而set操作通常涉及到存储层查询,这样就给mysql大大增加了压力,最坏的情况可能直接导致mysql宕机,总结一句话就是有大量线程来重建缓存,造成后端负载加大,甚至让应用直接奔溃.

我们可以使用redis的分布式锁来解决这一个问题,伪代码如下

//先读取缓存
value=redis.get(key)
if(value == null){
    mutexKey=time()
    if(redis.set("mutex".key,mutexKey,'px',1000,'nx')){
        // 读取数据库等系列操作,并计算,获得变量v
        value=计算获得value
        redis.set(key,value)
    }else{
        //进入这个分支的就是并发线程
        sleep(0.1)//阻塞0.1s,时间需要根据实际情况而定,必须保证大于value复杂计算的时间
        value=redis.get(key)
    }
    
}
....
处理业务
.....
return value

上面这段代码就保证了只有一个线程会到达后端的存储层,成功缓解了存储层的压力。n

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,846评论 2 374
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,146评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,789评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,656评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,467评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,870评论 3 389
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,500评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,790评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,828评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,628评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,449评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,881评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,077评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,376评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,879评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,089评论 2 338