不用windbg解决High CPU的一个案例
场景:一个400-700 requests /sec的web app再一次较大改版后遇到了high cpu的问题,当负载攀高时系统相应开始变慢。
基础:本文着重在于讲述分析问题的过程。lixiong写的windows用户态程序高效排错一书从一开始就在讲述一个道理:找到问题的关键不在使用windbg的过程,而在于对问题的分析。
首先,这是一个用于web聊天和消息投递的web应用程序,所以一般情况下不存在对大数据的传输和处理。cpu在75-90%之间波动。而且程序使用了Lion的异常捕获组件,也就是说所有能看到的asp.net的导致yellow screen of death的情况,是都能捕获到的。那么asp.net的程序出问题了,就先从perfmon里看看情况。
打开perfmon,添加了几个counter,等一会,看看平均值:
Requests/sec:450
Request in application queen:0(没有hang)
Virtual Bytes和Private Bytes都平稳,两者关系大概为4倍大小。
# Bytes in all heaps:波动较剧烈
......
......
web系统响应慢,一般我会先考虑是不是有什么lock导致的hang的情况,大家都在等,从而导致Request in application queen值不为0,当这个值继续增大的某个程度的时候,访问程序就直接扔给你Error 503了。但是这里这个值为0,说明hang的可能性太小了。
那Virtual Bytes=4倍大小的Private Bytes,这个看起来有点问题,通常来说,Virtual Bytes不会大于2倍大小的Private Bytes,否则应该考虑是不是有fragment的问题了,检查web.config,debug=false没问题,这时候觉得fragment的可能性也比较小了。
# Bytes in all heaps这个值应该是只有GC在collect的时候才会变化,但是Virtual Bytes和Private Bytes都是平稳的,memory leak的可能性基本没有,程序也没有使用有特别问题的非托管代码。那# Bytes in all heaps活动比较频繁,是为什么?
既然常用的counter不能解释更多问题了,就再加上了几个与.net相关的计数器。一下子# of exceps thrown / sec的值让我大吃一惊:这个程序每秒钟抛出480个异常!
# of exceps thrown / sec计数器是指示每秒种出现的异常的个数,这里的异常包括已经catch到的。一般来说该值在20一下是正常的。前文已经提到过我并没有log到这么多的exception,也就是说这些exception全部是.net或asp.net抛出来的。这里我把.net和asp.net分开说是因为原因有2个:
- 如果.net平台调用的一些非托管资源返回一些不正确的HResult格式,那就会有一个异常出来。
- asp.net框架在某些地方也会抛一个异常,但是自己就处理了。只不过我们没有注意到。
OK,先不急着去抓dump找具体原因,继续看perfmon。删除掉一些counter,保留几个,调整一下颜色和比例后观察一段时间:CPU、异常个数和请求数的趋势大致相同,差值也基本恒定,那至少我们可以看出:
- cpu只所以这么high,exception功不可没。
- 几乎每个请求都会抛出异常,但肯定不是所有的请求都抛出异常(否则应该是接近正比关系)
- 总有一些请求每次抛出不止一个异常(还是通过不是正比关系来判断)
往往一个大型的web应用程序只有固定的几个页面会有较大较频繁的访问量(当然在spider活动不频繁的时段就是特殊情况了)。什么样的代码会这么频繁被调用?通常来说三种可能性:HttpMoudle、HttpHandler和像自定义的Page基类一样的东西。
按照顺序逐个去找,最好找到了一个地方,在HttpHandler里使用了context.Server.Transfer()方法。HttpServerUtitility类的Transfer()方法用Reflector来看一下:
2{
3 Page handler = this._context.Handler as Page;
4 if ((handler != null) && handler.IsCallback)
5 {
6 throw new ApplicationException(SR.GetString("Transfer_not_allowed_in_callback"));
7 }
8 this.Execute(path, null, preserveForm);
9 this._context.Response.End();
10}
看到问题了吧:如果handler有问题,那么throw一个ApplicationException出来,否则调用了Response.End()方法,而Response.End()也会throw一个:(感谢overred指正)
2 {
3 if (this._context.IsInCancellablePeriod)
4 {
5 InternalSecurityPermissions.ControlThread.Assert();
6 Thread.CurrentThread.Abort(new HttpApplication.CancelModuleException(false));
7 }
8 else if (!this._flushing)
9 {
10 this.Flush();
11 this._ended = true;
12 if (this._context.ApplicationInstance != null)
13 {
14 this._context.ApplicationInstance.CompleteRequest();
15 }
16 }
17 }
这些handler都是在处理那些被频繁请求的页面,一个请求就至少有2个异常。
这里想留下两个要点一起与大家讨论:
- 为什么CPU高?撇开其他部分不说,创建exception,把callstack和那些数据收集起来是很昂贵的操作。之后GC有要负责清理这些,也是expensive way。
- 一开始就提到了Virtual Bytes和Private Bytes的比例是在4倍左右,Virtual Bytes为为什么这么高?仅仅因为cpu多吗?(在taskmgr里可以看到4 cpu,x86)
请您一定留下宝贵的想法和分析思路。谢谢您。