阅读 5293

【高并发优化实践】10倍请求压力来袭,你的系统会被击垮吗?【石杉的架构笔记】

欢迎关注个人公众号:石杉的架构笔记(ID:shishan100)

周一至周五早8点半!精品技术文章准时送上!

上篇文章一次 JVM FullGC的背后,竟隐藏着惊心动魄的线上生产事故!,给大家讲了一个线上系统因为JVM FullGC异常宕机的case。
这篇文章,我们继续给大家聊聊另外一个线上系统在生产环境遇到的问题。

一、背景介绍

背景情况是这样:线上一个系统,在某次高峰期间MQ中间件故障的情况下,触发了降级机制,结果降级机制触发之后运行了一小会儿,突然系统就完全卡死,无法响应任何请求。

给大家简单介绍一下这个系统的整体架构,这个系统简单来说就是有一个非常核心的行为,就是往MQ里写入数据,但是这个往MQ里写入的数据是非常核心及关键的,绝对不容许有丢失。

所以最初就设计了一个降级机制,如果一旦MQ中间件故障,那么这个系统立马就会把核心数据写入本地磁盘文件。

额外提一句,如果有同学不太清楚MQ中间件的概念,建议看一下之前发的一篇文章「Java进阶面试系列之一」你们系统架构中为何要引入消息中间件?,先对MQ中间件这个东西做一个基本的了解。

但是如果说在高峰期并发量比较高的情况下,接收到一条数据立马同步写本地磁盘文件,这个性能绝对是极其差的,会导致系统自身的吞吐量瞬间大幅度下降,这个降级机制是绝对无法在生产环境运行的,因为自己就会被高并发请求压垮。

因此当时设计的时候,对降级机制进行了一番精心的设计。

我们的核心思路是一旦MQ中间件故障,触发降级机制之后,系统接收到一条请求不是立马写本地磁盘,而是采用内存双缓冲 + 批量刷磁盘的机制

简单来说,系统接收到一条消息就会立马写内存缓冲,然后开启一个后台线程把内存缓冲的数据刷新到磁盘上去。

整个过程,大家看看下面的图,就知道了。


这个内存缓冲实际在设计的时候,分为了两个区域。

一个是current区域,用来供系统写入数据,另外一个是ready区域,用来供后台线程刷新数据到磁盘里去。

每一块内存区域设置的缓冲大小是512kb,系统接收到请求就写current缓冲区,但是current缓冲区总共就512kb的内存空间,因此一定会写满。

同样,大家结合下面的图,一起来看看。


current缓冲区写满之后,就会交换current缓冲区和ready缓冲区。交换过后,ready缓冲区承载了之前写满的512kb的数据。

然后current缓冲区此时是空的,可以继续接着系统继续将新来的数据写入交换后的新的current缓冲区。

整个过程如下图所示:


此时,后台线程就可以将ready缓冲区中的数据通过Java NIO的API,直接高性能append方式的写入到本地磁盘文件里。

当然,这里后台线程会有一整套完善的机制,比如说一个磁盘文件有固定大小,如果达到了一定大小,自动开启一个新的磁盘文件来写入数据。


二、埋下隐患

好!通过上面一套机制,即使是高峰期,也能顺利的抗住高并发的请求,一切看起来都很美好!

但是,当时这个降级机制在开发时,我们采取的思路,为后面埋下了隐患!

当时采取的思路是:如果current缓冲区写满了之后,所有的线程全部陷入一个while循环无限等待。

等到什么时候呢?一直需要等到ready缓冲区的数据被刷到磁盘文件之后,清空掉ready缓冲区,然后跟current缓冲区进行交换。

这样current缓冲区要再次变为空的缓冲区,才可以让工作线程继续写入数据。

但是大家有没有考虑过一个异常的情况有可能会发生?

就是后台线程刷新ready缓冲区的数据到磁盘文件,实际上也是需要一点时间的。

万一在他刷新数据到磁盘文件的过程中,current缓冲区突然也被写满了呢?

此时就会导致系统的所有工作线程无法写入current缓冲区,线程全部卡死。

给大家上一张图,看看这个问题!


这个就是系统的降级机制的双缓冲机制最根本的问题了,在开发好这套降级机制之后,采用正常的请求压力测试过,发现两块缓冲区在设置为512kb的情况下,运作良好,没有什么问题。


三、高峰请求,问题爆发


但是问题就出在高峰期上了。某一次高峰期,系统请求压力达到了平时的10倍以上。

当然正常流程下,高峰期的时候,写请求其实也是直接全部写到MQ中间件集群去的,所以哪怕你高峰期流量增加10倍也无所谓,MQ集群是可以天然抗高并发的。

但是当时不幸的是,在高峰期的时候,MQ中间件集群突然临时故障,这也是一年遇不到几次的。

这就导致这个系统突然触发了降级机制,然后就开始写入数据到内存双缓冲里面去。

要知道,此时是高峰期啊,请求量是平时正常的10倍!因此10倍的请求压力瞬间导致了一个问题的发生。

这个问题就是瞬时涌入的高并发请求一下将current缓冲区写满,然后两个缓冲区交换,后台线程开始刷新ready缓冲区的数据到磁盘文件里去。

结果因为高峰期请求涌入过快,导致ready缓冲区的数据还没来得及刷新到磁盘文件,此时current缓冲区又突然写满了。。。

这就尴尬了,线上系统瞬间开始出现异常。。。

典型的表现就是,所有机器上部署的实例全部线程都卡死,处于wait的状态。


四、定位问题,对症下药

于是,这套系统开始在高峰期无法响应任何请求。后来经过线上故障紧急排查、定位和抢修,才解决了这个问题。

其实说来解决方法也很简单,我们通过jvm dump出来快照进行分析,查看系统的线程具体是卡在哪个环节,然后发现大量线程卡死在等待current缓冲区的地方。

这就很明显知道原因了,解决方法就是对线上系统扩容双段缓冲的大小,从512kb扩容到一个缓冲区10mb。

这样在线上高峰期的情况下,也可以稳稳的让降级机制的双缓冲机制流畅的运行,不会说瞬间高峰涌入的请求打满两块缓冲区。

因为缓冲区越大,就可以让ready缓冲区被flush到磁盘文件的过程中,current缓冲区没那么快被打满。

但是这个线上故障反馈出来的一个教训,就是对系统设计和开发的任何较为复杂的机制,都必须要参照线上高峰期的最大流量来压力测试。只有这样,才能确保任何在系统上线的复杂机制可以经得起线上高峰期的流量的考验。

End



如有收获,请帮忙转发,您的鼓励是作者最大的动力,谢谢!


一大波微服务、分布式、高并发、高可用的原创系列文章正在路上

欢迎扫描下方二维码,持续关注:


石杉的架构笔记(id:shishan100)

十余年BAT架构经验倾囊相授


推荐阅读:

1、拜托!面试请不要再问我Spring Cloud底层原理

2、【双11狂欢的背后】微服务注册中心如何承载大型系统的千万级访问?

3、【性能优化之道】每秒上万并发下的Spring Cloud参数优化实战

4、微服务架构如何保障双11狂欢下的99.99%高可用

5、兄弟,用大白话告诉你小白都能听懂的Hadoop架构原理

6、大规模集群下Hadoop NameNode如何承载每秒上千次的高并发访问

7、【性能优化的秘密】Hadoop如何将TB级大文件的上传性能优化上百倍

8、拜托,面试请不要再问我TCC分布式事务的实现原理坑爹呀!

9、【坑爹呀!】最终一致性分布式事务如何保障实际生产中99.99%高可用?

10、拜托,面试请不要再问我Redis分布式锁的实现原理!

11、【眼前一亮!】看Hadoop底层算法如何优雅的将大规模集群性能提升10倍以上?

12、亿级流量系统架构之如何支撑百亿级数据的存储与计算

13、亿级流量系统架构之如何设计高容错分布式计算系统

14、亿级流量系统架构之如何设计承载百亿流量的高性能架构

15、亿级流量系统架构之如何设计每秒十万查询的高并发架构

16、亿级流量系统架构之如何设计全链路99.99%高可用架构

17、七张图彻底讲清楚ZooKeeper分布式锁的实现原理

18、大白话聊聊Java并发面试问题之volatile到底是什么?

19、大白话聊聊Java并发面试问题之Java 8如何优化CAS性能?

20、大白话聊聊Java并发面试问题之谈谈你对AQS的理解?

21、大白话聊聊Java并发面试问题之公平锁与非公平锁是啥?

22、大白话聊聊Java并发面试问题之微服务注册中心的读写锁优化

23、互联网公司的面试官是如何360°无死角考察候选人的?(上篇)

24、互联网公司面试官是如何360°无死角考察候选人的?(下篇)

25、Java进阶面试系列之一:哥们,你们的系统架构中为什么要引入消息中间件?

26、【Java进阶面试系列之二】:哥们,那你说说系统架构引入消息中间件有什么缺点?

27、【行走的Offer收割机】记一位朋友斩获BAT技术专家Offer的面试经历

28、【Java进阶面试系列之三】哥们,消息中间件在你们项目里是如何落地的?

29、【Java进阶面试系列之四】扎心!线上服务宕机时,如何保证数据100%不丢失?

30、一次JVM FullGC的背后,竟隐藏着惊心动魄的线上生产事故!



关注下面的标签,发现更多相似文章
评论