1 介绍
一次缓存雪崩的灾难复盘中,我们比较清晰的描述了缓存雪崩、穿透、击穿的各自特征和解决方案,想详细了解的可以移步。
最近在配合HR筛选候选人,作为大厂的业务方向负责人,招人主要也是我们自己团队在用,而缓存是必不可少的面试选项之一。下面我们就来聊一聊在特定业务场景下缓存击穿和雪崩的应对场景!
2 问题背景
一个核心的应用或者服务(比如微信、钉钉、百度APP),高峰QPS是百万甚至是千万
应用缓存了用户的基本信息,如(姓名、性别、职业、地址等),假设以为用户Id为Cache的key,那每个用户都有一个基础信息的缓存。
因为不知名的原因,导致缓存都丢了(可能是缓存集体过期、故障导致缓存失效、程序bug导致缓存误删、服务器重启导致内存清理)。
恰巧是访问高峰期(比如9点早高峰),千百万的请求狂奔而来,查不到缓存,透过缓存层直接投入数据库。
基于磁盘的数据库的访问效率,性能,抗击打能力远逊于高速缓存,数据库很容易被打垮,造成服务雪崩。
4 候选人的各种答案(综合整理)
4.1 缓存预热
4.2 非一致的过期时间
缓存既然大部分是在高峰期(9~10点)创建的(假设Cache的Expire Time都一样,比如8h),那很有可能失效时间会很接近。几乎同一时间一起失效,这样确实也会引起群起创建的情况,也会导致上面说的击穿的情况发生。
我们在创建同一类型的批次缓存的时候,会采用3-4-3 分布原则。比如一个缓存的Expire Time 是 10H,
那么就是3H + 4H * random( + 3h ,来进行错开!
4.3 消息聚合缓存
为什么每个用户的基本信息都独立存储一个缓存呢?可不可以按照用户类型分片,一类的用户合在一起不是只要查询一次,不会出现峰值期群起攻击数据库的情况。
用户信息还算修改频率比较低的,你的积分信息,购物车可是很高频变动的,这种的就不能这么干了。
4.4 削峰、加锁、限流
4.4.1 削峰
4.4.2 加锁
同一个用户的信息查询只让第一个请求进入,进入之后加锁,在获取到数据库信息并更新缓存之后释放锁,
这样单一个信息只请求一次!
4.4.3 限流
缺点:
但凡用锁,排队之类的方案,无一例外的会大幅度降低服务的吞吐率,造成用户长时间等待,体验感下降,这在各大型APP(淘宝、微信、百度APP)上是完全不允许的,也不会这么干。
限流也是一样的道理,限流一般是对服务的限流,而难以细粒度到只对某个信息类型的限流。而服务级别限流会误伤其他操作,比如获取排班、排课、获取购物车等非瓶颈的宽松的查询也被限了。当然,现在的限流也可以细粒度到某个或者某几个接口,所以可以将查询用户信息合在一个接口里做一下限流。但是限流也代表部分用户拿不到正确的信息,是一种降级的行为。
4.5 短暂降级之备选缓存
你的缓存层存在主备场景,他们之间定时异步同步,所以存在短暂数据不一致。
当你的主服务挂了之后,降级去读备服务,数据时效性没那么高,但是也避免了数据库被打穿的情况发生。
4.6 短暂降级值客户端缓存(Redis 6.0)
追求性能极致:客户端缓存带来的革命》。
类似4.5做法,客户端缓存时效性会差一点,毕竟存在订阅跟同步的过程,数据没那么新。但是避免大量的请求直接上缓存服务,又因无效的缓存服务有把压力转移给数据库。
4.7 短暂降级之空初始值
可以看出,整个过程中我们牺牲了A、B、C、D的请求,他们拿回了一个空值或者默认值,但是这局部的降级却保证整个数据库系统不被拥堵的请求击穿。
5 总结
在不同的场景下各种方法都有各自的优缺点,我们要做的就是根据实际的应用场景来判断和抉择。