优化你的DiscuzNT3.0,让它跑起来(6)在线人数和Regex.IsMatch()引发的hang
注:本文仅针对 DiscuzNT3.0, sqlserver 2000版本,其他版本请勿对号入座。
你没看错标题,的确是 在线人数和Regex.IsMatch()引发的hang。事情是这样的,就在今天我们的论坛出现的挂起问题,当时刚好赶上了抓dump文件。于是就有了今天这篇文章。
我们先用windbg看看论坛当时在干什么吧。
1. 打开文件,运行 .load sos, 因为是hang,所以当然是要运行 !syncblk , 下面是运行结果:
0:000> .load sos
从上面看到,持有锁的线程是48 ,持有的对象是System.Collections.Generic.LinkedList,
2. 我们看看线程48在干什么, ~48s 切换到线程,!clrstack 看看执行的代码。
--------------------------
我们从上面看到程序调用了
Discuz.Common.TypeConverter.StrToInt() 这个方法,然后进入 System.Text.RegularExpressions.dll,最后停留在System.Text.RegularExpressions.Regex.LookupCachedAndUpdate() 方法, 我们从dnt3.0的程序一步步来看看。
2 /// 将对象转换为Int32类型
3 /// </summary>
4 /// <param name="str">要转换的字符串</param>
5 /// <param name="defValue">缺省值</param>
6 /// <returns>转换后的int类型结果</returns>
7 public static int StrToInt(string str, int defValue)
8 {
9 if (string.IsNullOrEmpty(str) || str.Trim().Length >= 11 || !Regex.IsMatch(str.Trim(), @"^([-]|[0-9])[0-9]*(\.\w*)?$"))
10 return defValue;
11
12 int rv;
13 if (Int32.TryParse(str, out rv))
14 return rv;
15
16 return Convert.ToInt32(StrToFloat(str, defValue));
17 }
请出reflector
2 {
3 return new Regex(pattern, RegexOptions.None, true).IsMatch(input);
4 }
继续reflector
2 {
3 CachedCodeEntry cachedAndUpdate = null;
4 string threeLetterWindowsLanguageName = null;
5 if (pattern == null)
6 {
7 throw new ArgumentNullException("pattern");
8 }
9 if ((options < RegexOptions.None) || ((((int) options) >> 10) != 0))
10 {
11 throw new ArgumentOutOfRangeException("options");
12 }
13 if (((options & RegexOptions.ECMAScript) != RegexOptions.None) && ((options & ~(RegexOptions.CultureInvariant | RegexOptions.ECMAScript | RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase)) != RegexOptions.None))
14 {
15 throw new ArgumentOutOfRangeException("options");
16 }
17 if ((options & RegexOptions.CultureInvariant) != RegexOptions.None)
18 {
19 threeLetterWindowsLanguageName = CultureInfo.InvariantCulture.ThreeLetterWindowsLanguageName;
20 }
21 else
22 {
23 threeLetterWindowsLanguageName = CultureInfo.CurrentCulture.ThreeLetterWindowsLanguageName;
24 }
25 string[] strArray = new string[] { ((int) options).ToString(NumberFormatInfo.InvariantInfo), ":", threeLetterWindowsLanguageName, ":", pattern };
26 string key = string.Concat(strArray);
27 cachedAndUpdate = LookupCachedAndUpdate(key);
28 this.pattern = pattern;
29 this.roptions = options;
30 if (cachedAndUpdate == null)
31 {
32 RegexTree t = RegexParser.Parse(pattern, this.roptions);
33 this.capnames = t._capnames;
34 this.capslist = t._capslist;
35 this.code = RegexWriter.Write(t);
36 this.caps = this.code._caps;
37 this.capsize = this.code._capsize;
38 this.InitializeReferences();
39 t = null;
40 if (useCache)
41 {
42 cachedAndUpdate = this.CacheCode(key);
43 }
44 }
45 else
46 {
47 this.caps = cachedAndUpdate._caps;
48 this.capnames = cachedAndUpdate._capnames;
49 this.capslist = cachedAndUpdate._capslist;
50 this.capsize = cachedAndUpdate._capsize;
51 this.code = cachedAndUpdate._code;
52 this.factory = cachedAndUpdate._factory;
53 this.runnerref = cachedAndUpdate._runnerref;
54 this.replref = cachedAndUpdate._replref;
55 this.refsInitialized = true;
56 }
57 if (this.UseOptionC() && (this.factory == null))
58 {
59 this.factory = this.Compile(this.code, this.roptions);
60 if (useCache && (cachedAndUpdate != null))
61 {
62 cachedAndUpdate.AddCompiled(this.factory);
63 }
64 this.code = null;
65 }
66 }
这个代码量较大,找到关键点 LookupCachedAndUpdate
{
lock (livecode)
{
for (LinkedListNode<CachedCodeEntry> node = livecode.First; node != null; node = node.Next)
{
if (node.Value._key == key)
{
livecode.Remove(node);
livecode.AddFirst(node);
return node.Value;
}
}
}
return null;
}
终于找到这个lock的对象了,看看他是什么类型的,和我们通过windbg看到的一样吗
internal static LinkedList<CachedCodeEntry> livecode;
果然一样, 这下应该放心了,就是这里的lock引起了hang,但是我们应该经常用Regex.IsMatch()的,也没见引起这个问题啊,为什么这里???
我们看看在线人数有多少,如果在线人数比较多,访问的人数也多,那可能性就很大了,我们来看看到底有多少人在线。
运行 !dso,看看本线程对象。
----------------------
找到上面的
Discuz.Common.Generic.List 的地址 068e2dc4 , 运行 !do 068e2dc4
从上面的size可以看到在线人数是472人,就是说这个48这个线程要lock 472 次,如果有n个人访问那后面的人真的要等不少时候了。
话说StrToInt()这个方法为什么要用Regex.IsMatch()呢,string 转换成 int 一般 int.TryParse()也足够了。不过从这里我才发现Regex.IsMatch() 里面原来还有个lock,不然还真不知道,也算是收获不小啊。