5

架构与思维:再聊缓存击穿,面试是一场博弈 - Hello-Brand

 1 year ago
source link: https://www.cnblogs.com/wzh2010/p/16929808.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在之前的一篇文章《一次缓存雪崩的灾难复盘》中,我们比较清晰的描述了缓存雪崩、穿透、击穿的各自特征和解决方案,想详细了解的可以移步。
最近在配合HR筛选候选人,作为大厂的业务方向负责人,招人主要也是我们自己团队在用,而缓存是必不可少的面试选项之一。下面我们就来聊一聊在特定业务场景下缓存击穿和雪崩的应对场景!

2 问题背景

  • 一个核心的应用或者服务(比如微信、钉钉、百度APP),高峰QPS是百万甚至是千万

★ 分析:上述类型的应用具有很明显的峰值 高斯分布的特征,就是9~10点是用户早高峰。微信是,百度APP是,钉钉也是,钉钉一般给政企、教学等使用,通用是10点左右峰值期,每天的峰值如下:

image

  • 应用缓存了用户的基本信息,如(姓名、性别、职业、地址等),假设以为用户Id为Cache的key,那每个用户都有一个基础信息的缓存。
  • 因为不知名的原因,导致缓存都丢了(可能是缓存集体过期、故障导致缓存失效、程序bug导致缓存误删、服务器重启导致内存清理)。
  • 恰巧是访问高峰期(比如9点早高峰),千百万的请求狂奔而来,查不到缓存,透过缓存层直接投入数据库。
  • 基于磁盘的数据库的访问效率,性能,抗击打能力远逊于高速缓存,数据库很容易被打垮,造成服务雪崩。

4 候选人的各种答案(综合整理)

4.1 缓存预热

既然是可预见的峰值期,那么缓存预热是一个好办法,比如在9 ~ 10点是高峰期,在7 ~ 9点这两个小时中,可以均匀的把部分缓存做上。

image

缺点:这种仅仅只能解决可预见的缓存失效情况。如果是突发缓存失效情况,假设在10点高峰期因为某些原因(比如上面说的 故障导致缓存失效、程序bug导致缓存误删、服务器重启导致内存清理)是没有效果的。

4.2 非一致的过期时间

缓存既然大部分是在高峰期(9~10点)创建的(假设Cache的Expire Time都一样,比如8h),那很有可能失效时间会很接近。几乎同一时间一起失效,这样确实也会引起群起创建的情况,也会导致上面说的击穿的情况发生。
我们在创建同一类型的批次缓存的时候,会采用3-4-3 分布原则。比如一个缓存的Expire Time 是 10H,
那么就是3H + 4H * random() + 3h ,来进行错开!

image

缺点:同4.1类似,仅仅解决可预见的问题,对突发故障导致的无预期的缓存失效毫无办法。

4.3 消息聚合缓存

为什么每个用户的基本信息都独立存储一个缓存呢?可不可以按照用户类型分片,一类的用户合在一起不是只要查询一次,不会出现峰值期群起攻击数据库的情况。

说明:只有信息修改率非常低的缓存才适合聚合在一个缓存值中,大部分情况下不会这么做。比如你的缓存中聚合了1W个人的信息,Value非常大,但凡其中一个信息修改,那么这个缓存就要更新,不然应用读取到的信息就没有时效性,大Value的缓存频繁的存取是一个很不友好的事情。
用户信息还算修改频率比较低的,你的积分信息,购物车可是很高频变动的,这种的就不能这么干了。

4.4 削峰、加锁、限流

4.4.1 削峰

引进消息队列之类的中间件,将用户的请求放入队列,逐一执行,避免拥挤请求!

4.4.2 加锁

同一个用户的信息查询只让第一个请求进入,进入之后加锁,在获取到数据库信息并更新缓存之后释放锁,
这样单一个信息只请求一次!

4.4.3 限流

为了避免把服务端打挂,在上线前做一次无缓存压测,看数据库与服务端能支撑的最大值。并设置成限流的阈值,保证不会超过服务所能承载的压力,避免过载!

  • 但凡用锁,排队之类的方案,无一例外的会大幅度降低服务的吞吐率,造成用户长时间等待,体验感下降,这在各大型APP(淘宝、微信、百度APP)上是完全不允许的,也不会这么干。
  • 限流也是一样的道理,限流一般是对服务的限流,而难以细粒度到只对某个信息类型的限流。而服务级别限流会误伤其他操作,比如获取排班、排课、获取购物车等非瓶颈的宽松的查询也被限了。当然,现在的限流也可以细粒度到某个或者某几个接口,所以可以将查询用户信息合在一个接口里做一下限流。但是限流也代表部分用户拿不到正确的信息,是一种降级的行为。

备注:数据库也有限流方案,细粒度到这个层级更好

4.5 短暂降级之备选缓存

你的缓存层存在主备场景,他们之间定时异步同步,所以存在短暂数据不一致。
当你的主服务挂了之后,降级去读备服务,数据时效性没那么高,但是也避免了数据库被打穿的情况发生。

image

4.6 短暂降级值客户端缓存(Redis 6.0)

参考Redis 6.0的 Client Side Cache,看我这篇《追求性能极致:客户端缓存带来的革命》。
类似4.5做法,客户端缓存时效性会差一点,毕竟存在订阅跟同步的过程,数据没那么新。但是避免大量的请求直接上缓存服务,又因无效的缓存服务有把压力转移给数据库。

image

4.7 短暂降级之空初始值

这是一种短暂降级的方式,大概流程如下:

image

可以看出,整个过程中我们牺牲了A、B、C、D的请求,他们拿回了一个空值或者默认值,但是这局部的降级却保证整个数据库系统不被拥堵的请求击穿。

在不同的场景下各种方法都有各自的优缺点,我们要做的就是根据实际的应用场景来判断和抉择。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK