不用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个:

  1. 如果.net平台调用的一些非托管资源返回一些不正确的HResult格式,那就会有一个异常出来。
  2. asp.net框架在某些地方也会抛一个异常,但是自己就处理了。只不过我们没有注意到。

OK,先不急着去抓dump找具体原因,继续看perfmon。删除掉一些counter,保留几个,调整一下颜色和比例后观察一段时间:CPU、异常个数和请求数的趋势大致相同,差值也基本恒定,那至少我们可以看出:

  1. cpu只所以这么high,exception功不可没。
  2. 几乎每个请求都会抛出异常,但肯定不是所有的请求都抛出异常(否则应该是接近正比关系)
  3. 总有一些请求每次抛出不止一个异常(还是通过不是正比关系来判断)

往往一个大型的web应用程序只有固定的几个页面会有较大较频繁的访问量(当然在spider活动不频繁的时段就是特殊情况了)。什么样的代码会这么频繁被调用?通常来说三种可能性:HttpMoudle、HttpHandler和像自定义的Page基类一样的东西。

按照顺序逐个去找,最好找到了一个地方,在HttpHandler里使用了context.Server.Transfer()方法。HttpServerUtitility类的Transfer()方法用Reflector来看一下:

 1public void Transfer(string path, bool preserveForm)
 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指正)

 1 public void End()
 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个异常。

这里想留下两个要点一起与大家讨论:

  1. 为什么CPU高?撇开其他部分不说,创建exception,把callstack和那些数据收集起来是很昂贵的操作。之后GC有要负责清理这些,也是expensive way。
  2. 一开始就提到了Virtual Bytes和Private Bytes的比例是在4倍左右,Virtual Bytes为为什么这么高?仅仅因为cpu多吗?(在taskmgr里可以看到4 cpu,x86)

请您一定留下宝贵的想法和分析思路。谢谢您。

posted @ 2008-04-17 13:47  new 维生素C.net()  阅读(2651)  评论(12编辑  收藏  举报