架构与思维:一次缓存雪崩的灾难复盘

1 真实案例

云办公系统用户实时信息查询功能优化发布之后,系统发生宕机事件(系统挂起,页面无法加载)。

1.1 背景

我们IM原有的一个功能,当鼠标移动到用户头像的时候,会显示出用户的基本信息。信息比较简单,只包含简单的用户名、昵称、性别、邮箱、电话等基本数据,

这是一个典型的数据查询,大概过程如下左侧,访问用户基本信息的时候会先去Redis中查一下,如果不存在,就把大约2W左右的用户数据一次性取出来,保存在Redis中,因为用户基本信息在同一张表上,用户信息表的数据量也很少,所以一直也没什么问题。

过程如下图左侧所示。

image

后续对功能做了优化,原有采集的信息除了用户的基本信息之外,还采集了教育经历、工作经历、所获勋章等。

这些信息存储在不同的表里面,所以采集过程是一个复杂的联表查询,特别是有些基础表数据量比较大,执行效率也是比较慢的。

如果把所有用户全部取出来并存储在一个Redis节点中,明显已经不适用,一个是批量查询导致数据库执行效率慢,一个是Redis单节点数据太大。

所以开发同学做了下优化,每次只取单个用户的综合信息存在Redis中,一个用户建一个缓存,如上图右侧所示。

1.2 问题处理

这种做法看着没啥问题,当晚发布后,在第二天的上午10点~11点就发生了系统瓶颈卡顿,最后挂起的情况,数据库的内存、CPU全部飙上去了。

第一时间的处理方法是降级,程序回滚到之前只提供基本信息的阶段,其他的前端默认显示空信息。接着就是对问题进行分析了,后确认原因是产生了 缓存雪崩了。

新发布的系统,缓存池是空的,在早上10点高峰期的时候,大量的人员到IM上进行访问,系统开始初次建立每个人的缓存信息,大量的请求查询不到缓存,直接透过缓存池投向数据库,造成瞬时DB请求量井喷。这是典型的缓存雪崩了。

同时因为,失效时间相近(8小时失效),所以也有潜在的缓存雪崩。

应急处理方案:适当处理缓存的机制,采用布隆过滤器、空初始值、随机缓存失效时间方式来预防缓存击穿和缓存雪崩的产生。

最终解决方案:改回原来缓存全公司员工信息的方式,根据执行计划和SlowLog,优化获取员工信息的SQL脚本,去掉不需要的字段和无意义的连接。

2 缓存雪崩

2.1 概念

缓存雪崩是指大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。

上面的哪个问题,初次访问的数据都是未建立缓存的,跟同时失效的情况一样,当峰值期到来的时候,会大量的请求查询不到缓存,直接透过缓存池投向数据库,造成瞬时DB请求量井喷。

2.2 解决方案分析

2.2.1 缓存集群+数据库集群

在系统容量设计的时候,应该能够预见后期会有大量的请求,所以在发生雪崩前对缓存集群实现高可用,如果是使用 Redis,可以使用 主从+哨兵 ,Redis Cluster 来避免 Redis 全盘崩溃的情况。

同样的,也需要对数据库进行高可用保障,因为透过缓存之后,真正考验的是数据库的抗压能力。所以 1主N从 甚至 数据库集群 是我们需要重点去考虑的。

2.2.2 适当的限流、降级

可以使用 Hystrix进行限流 + 降级 ,比如像上面那种情况,一下子来了1W个请求,不是当前系统的吞吐能力能够承受的,假设单秒TPS的能力只能是 5000个,那么剩余的 5000 请求就可以走限流逻辑。

可以设置一些默认值,然后调用我们自己降级逻辑去FallBack,保护最后的 MySQL 不会被大量的请求挂起。 除了Hystrix之外,阿里的Sentinel 和 Google的RateLimiter 都是不错的选择。

Sentinel 漏桶算法

image

RateLimiter 令牌桶算法

image

另外可以考虑使用用本地缓存来进行缓冲,在 Redis Cluster 不可用的时候,不至于全线崩溃。

2.2.3 随机过期时间

可以给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。

随机值我们团队的做法是:n * 3/4 + n * random() 。所以,比如你原本计划对一个缓存建立的过期时间为8小时,那就是6小时 + 0~2小时的随机值。

这样保证了均匀分布在 6~8小时之间。如图:

image

2.2.4 缓存预热

类似上面的那个案例,并不是还没过期,而是新功能发布,压根还没建设过缓存,所以可以在峰值期之前先做好部分缓存,避免瞬时压力太大。

所以如果10点是峰值期,那么可以预先在8~10点期间,可以逐渐的把大部分缓存建立起来。如图:

image.png

3 缓存穿透

3.1 概念

缓存穿透是指访问一个不存在的key,缓存不起作用,请求会穿透到DB,流量井喷时会导致DB挂掉。

比如 我们查询用户的信息,程序会根据用户的编号去缓存中检索,如果找不到,再到数据库中搜索。如果你给了一个不存在的编号:XXXXXXXX,那么每次都比对不到,就透过缓存进入数据库。

这样风险很大,如果因为某些原因导致大量不存在的编号被查询,甚至被恶意伪造编号进行攻击,那将是灾难。

3.2 解决方案分析

3.2.1 缓存空值

发生穿透的原因是缓存中没有存储这些空数据的key,或者压根这个数据的key是不会存在的,从而导致每次查询都进入数据库中。

我们就可以将这些key的值设置为null,并写到缓存池中。后面再出现查询这个key 的请求的时候,直接返回null,这样就在缓存池中就被判断返回了,压力在缓存层中,不会转移到数据库上。

3.2.2 BloomFilter

我们称作布隆过滤器,BloomFilter 类似于一个hbase set 用来判断某个元素(key)是否存在于某个集合中。

这种方式在大数据场景应用比较多,比如 Hbase 中使用它去判断数据是否在磁盘上。还有在爬虫场景判断url 是否已经被爬取过。

这种方案可以加在第一种方案中,在缓存之前在加一层 BloomFilter ,把存在的key记录在BloomFilter中,在查询的时候先去 BloomFilter 去查询 key 是否存在,如果不存在就直接返回,存在再走查缓存 ,投入数据库去查询,这样减轻了数据库的压力。

流程图如下:

image

3.2.3 两种方案的选择判断

前面说过,可能会存在一些恶意攻击,伪造出大量不存在的key ,这种情况下如果我们如果采用缓存空值的办法,就会产生大量不存在key的null数据。显然是不合适的,这时我们完全可以使用第二种方案进行过滤掉这些key。

所以,判断的依据是:

针对key非常多、请求重复率比较低的数据,我们就没有必要进行缓存,使用 BloomFilter 直接过滤掉。

而对于空数据的key有限的,重复率比较高的,我们则可以采用 缓存空值的办法 进行处理。

4 缓存击穿

4.1 概念

一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。(注意跟上面两种的区别)

4.2 解决方案

4.2.1 锁的方式

分布式锁场景,在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。

这种现象是多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它。

其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。

锁不好的地方就是在其他线程在拿不到锁的时候就等待,这个会造成系统整体吞吐量降低,用户体验度也不好。

4.2.2 空初始值

这是一种短暂降级的方式:

如果一个缓存失效的时候,有无数个请求狂奔而来,而第一个请求从进入缓存池,判空,再到数据库检索,再查询出结果并返回设置缓存的这个过程里,缓存是不存在的。

这个就很危险,超高并发下这个短暂的过程足已让千千万万请求投向数据库。更别提这可能是个慢查询,整个过程可能长达2s以上,那对数据库是一种非常大的伤害。

业内有一种做法叫做空初始值,短暂的局部降级来保证整个数据库系统不被击穿。大概流程如下:

image.png

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

这也是我面试中最喜欢问候选人的缓存类问题。

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

推荐阅读更多精彩内容