在这篇博文中,我们抛开对阿里云的怀疑,完全从ASP.NET的角度进行分析,看能不能找到针对问题现象的更合理的解释。
“黑色30秒”问题现象的主要特征是:排队的请求(Requests Queued)突增,到达HTTP.SYS的请求数(Arrival Rate)下降,QPS(Requests/Sec)下降,CPU消耗下降,Current Connections上升。
昨天晚上18:08左右发生了1次“黑色30秒”,正好借此案例分析一下。
1、为什么Requests Queued会突增?
最直接的原因是ASP.NET没有可用的线程处理当前请求。为什么会没有可用的线程呢?ASP.NET可用的线程毕竟是有限的,可能是当时瞬间的并发请求太多,ASP.NET来不及创建足够的线程处理这些请求。
我们来看一下ASP.NET中线程相关的设置——machine.config中的processModel(位于C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config)。
有4个相关设置:maxWorkerThreads(默认值是20), maxIoThreads(默认值是20), minWorkerThreads(默认值是1), minIoThreads(默认值是1)。(这些设置是针对每个CPU核)
我们用的就是默认设置,由于我们的Web服务器是8核的,于是实际的maxWorkerThreads是160,实际的maxIoThreads是160,实际的minWorkerThreads是8,实际的minIoThreads是8。
基于这样的设置,是不是如果瞬间并发请求是169,就会出现排队?不是的,ASP.NET没这么傻!因为CLR 1秒只能创建2个线程,等线程用完时才创建,黄花菜都凉了。我们猜测ASP.NET只是根据这个设置去预测线程池中的可用线程是不是紧张,是不是需要创建新的线程,以及创建多少线程。
那什么情况下会出现“黑色30秒”期间那样的大量请求排队?假如并发请求数平时是300,突然某个瞬间并发请求数是600,超出了ASP.NET预估的所需的可用线程数,于是那些拿不到线程的请求只能排队等待正在执行的请求释放线程以及CLR创建新的线程。随着时间的推移,释放出来的线程+新创建的线程足以处理这些排队的请求,就恢复了正常。
那如何验证这个猜测呢? 修改maxWorkerThreads, maxIoThreads, minWorkerThreads, minIoThreads的设置,让ASP.NET提供更多的可用线程,目前我们采用的设置如下:
<processModel enable="true" requestQueueLimit="5000" maxWorkerThreads="100" maxIoThreads="100" minWorkerThreads="50" minIoThreads="50"/>
如果采用这个设置之后,“黑色30秒”现象几乎不出现,就能验证问题出在这个地方。现在主站已经使用了这个设置,需要观察一段时间进行验证。
【启示】
1) 通过Windows性能监视器监视\ASP.NET\Requests Queued可以直观地评估ASP.NET应用程序的吞吐能力(throughput)。
2) 通过ASP.NET异步编程(async/await)可以有效减少可用线程紧张造成的请求排队问题。
2、为什么Arrival Rate会下降?
(上图中的橙色线条)
这是“黑色30秒”问题中最让人不解的地方,ASP.NET中请求再怎么排队,怎么会造成到达HTTP.SYS的请求数下降呢?一开始我们总是不相信是请求排队引起的Arrival Rate下降,但是监视图中却铁证如山。
写这篇博客之前,我们突然想通了!之前忽略了一个地方——当你打这篇博文时,第1个请求是html页面,如果这个请求得到正常响应,浏览器在加载这个页面时会发出多个ajax请求;如果第1个请求被排队,浏览器处于等待状态,后续的ajax请求就不会发出,这样到达HTTP.SYS的请求数就会下降。这也解释了为什么有时会在“黑色30秒”的中间阶段Arrival Rate会飙高,正是因为当时被排队的请求所对应的页面中有很多ajax,当它结束排队被执行后,后续的很多ajax请求(可能排队的很多是这样的请求)到达了HTTP.SYS。
于是,我们相信了是请求排队引起的Arrival Rate下降。
【启示】
不能把目光局限于当前看到的问题表现,而要综合考虑,将诸多因素联系起来理清各种现象之间的关系。
3、QPS下降
与Arrival Rate下降同理,QPS(Requests/Sec)与Arrival Rate是直接相关的,成正比关系。
于是,QPS下降也是因为请求排队。
4、CPU消耗下降
也是同理,Arrival Rate与QPS下降,说明CPU要干的活少了,自然消耗就下降。
于是,CPU消耗下降也是因为请求排队。
5、Current Connections上升
Current Connections是请求排队的一个直接表现,请求还没被执行,连接当然会保持着。
于是,Current Connection上升也是因为请求排队。
6、看一个新指标Requests Executing
(上图绿色的线条表示的是Requests Executing)