注:本文仅针对 DiscuzNT3.0, sqlserver 2000版本,其他版本请勿对号入座。
在本系列的第(4)篇中,我们提到过windbg,上次没能华丽登场的他,今天终于要一展才华了。解决问题是windbg与生俱来的功夫,他今天将怎么样来解决我们的问题,静听分解。
经过第(4)篇的优化之后,我们的论坛迎来了每日100万pv的突破性指标,相对于我们其他项目来说,这个量虽然不值一提,但毕竟这个项目是一切从0开始,能达到这个量也算是一个小突破,值得小庆祝一下。不过问题也随之而来了。我们的iis服务器的cpu占用率从之前的约30%,上升到了80%,按照我们的经验,这是不合理的,80%已经是一个危险的信号,随时都有可能引发server too busy。
从公司的利益角度来考虑,为了节约服务器资源和成本,当然要把cpu降下来;从技术人员的角度来考虑,如果你之前没解决过,这将是对自己的一个挑战,如果你解决了,就刚好是你价值的体现,正所谓乱世出英雄,这是一样的道理。好了,废话不多说,有请我们的今日之星windbg。
关于windbg,网上有很多介绍,我就不再累述了。我只发表自己对他的两点认识,比较通俗:
1)可以抓取内存包,即dump文件;
2)通过命令输出内存中正在执行的动作;
第3)步是技术人员自己要做的事情,通过命令输出的信息,分析找出问题的症结。这一步windbg是帮不了你的。
网上关于windbg的最常解决的问题一般是
1)程序崩溃;
2)程序挂起,比如死锁,超时;
3)高cpu;
4)内存泄露;
我们今天就来看看他是怎么找到 3)高cpu 的问题的。
windbg定位高cpu一般常用的方法和步骤是:
1)在高cpu的时候间隔约5秒钟抓一个包,抓3个左右,用adplus 命令;
2)通过 !runaway 命令找出每个包的前几个线程(约10个);
3)然后找出在每次的前10个线程里都出现的线程id;
4)通过 !clrstack 命名输出该线程的执行动作;
5)通过 第 4) 输出的信息找出问题的症结
(这是网上很多文章的说法,我觉得说的很模糊);
关于第5)点,我提点自己的看法,我一般就看这些个线程id都在干什么,如果他们10个里面有8个都停留在一个方法,那这个方法就很值得怀疑,是需要重点检查的,那到底dnt3.0情况如何,我们一步一步来看看。
1)首先抓包,在cmd下 通过adplus抓包,间隔10秒连抓3个(windbg命令的用法,不是本文的重点,google可以有一箩筐);
2)用windbg分别打开3个dump文件,运行!runaway命令,找到耗时的线程;
3个文件的截图如下:
第1张
第2张
第3张
3)找到10个最耗时的线程:11,12,21,22,34,38,40,46,47,48 。
4)通过!clrstack命令输出他们都在干什么(因为太多,这里只选几个代表性的)
线程 11 :
----------------
0:000> ~11s
。。。。。。这里省略若干字。
---------------------------
线程 12 :
----------
。。。。。。这里省略若干字。
---------------------------
线程 22:
--------
---------------------------
线程 48:
--------
---------------------------
看上面几个线程,虽然代码不完全相同,但是有几个地方是一样的,标红的地方就是, 上面标红的几个线程都执行到了
Discuz.Cache.DNTCache.RetrieveObject 这里面来,类似这样的代码,这10个线程里面有6个,不再一一列出,有2个没有跟踪到执行动作,另外2个线程在做其他事情(上面的48就是其中一个),我们现在只重点来关注这6个线程,因为她们都同时执行到了 Discuz.Cache.DNTCache.RetrieveObject ,我认为这里是存在性能问题的,来看看他的源码是怎么样的。
2 /// 返回指定XML路径下的数据项
3 /// </summary>
4 /// <param name="xpath">分级对象的路径</param>
5 /// <returns></returns>
6 public virtual object RetrieveOriginObject(string xpath)
7 {
8 if (applyMemCached)
9 {
10 //向缓存加入新的对象
11 return cs.RetrieveObject(xpath);
12 }
13 else
14 {
15 XmlNode node = objectXmlMap.SelectSingleNode(PrepareXpath(xpath));
16 if (node != null)
17 {
18 string objectId = node.Attributes["objectId"].Value;
19
20 return cs.RetrieveObject(objectId);
21 }
22 return null;
23 }
24 }
再看看
objectXmlMap.SelectSingleNode 这里是怎么实现的, 请出 reflector
{
XmlNodeList list = this.SelectNodes(xpath);
if ((list != null) && (list.Count > 0))
{
return list[0];
}
return null;
}
再看
this.SelectNodes(xpath);
{
XPathNavigator navigator = this.CreateNavigator();
if (navigator == null)
{
return null;
}
return new XPathNodeList(navigator.Select(xpath));
}
挺复杂的,再看
CreateNavigator
{
XmlDocument document = this as XmlDocument;
if (document != null)
{
return document.CreateNavigator(this);
}
return this.OwnerDocument.CreateNavigator(this);
}
哦,到这里看明白了, SelectSingleNode 经过了如下流程最终得到了结果:
创建 XmlDocument ——》 创建 XPathNavigator ——》 获取所有的节点Nodes ——》 返回第一个节点
这样一个流程下来会耗多少性能呢,这个每个人的测试方式不一样,所以结果可能也会不一样,感兴趣的童鞋可以自己测试一下,因为我不熟悉这种缓存方案,无法预知他的性能,我只好改成自己熟悉的,最后用的系统自带的HttpRuntime.Cache来做缓存。
改了缓存策略的效果还不错,cpu从之前的 >80% 降到了 <30%,抓个图: