zoukankan      html  css  js  c++  java
  • 优化你的DiscuzNT3.0,让它跑起来(4)asp.net 缓存和死锁

     注:本文仅针对 DiscuzNT3.0, sqlserver 2000版本,其他版本请勿对号入座.

     经过前面的几次优化之后我们的论坛终于稳定了一段时间,大概半年之后我们的论坛迎来了每天大约50万的pv,这时候论坛有开始出现了问题。症状是这样的:

    管理员发现,网站经常会打不开, 但是也不报错,好像永远一直在打开,直到浏览器认为它打不开了,这样的症状每天会出现几次,而且越来越频繁。每次发生这样的情况过后一般iis的事件查看器都会asp.net有死锁提示,于是我知道,我终于遇上传说中的死锁了,每次有死锁迹象的时候我都跟踪了一下sqlserver,发现数据库是正常的,那看来就是asp.net这边的问题了。

    可是DiscuzNT这么大的一个论坛,里面包含了十几个项目,项目如此之多,代码量如此之大,到底哪里出了问题呢,一下子还真不好定位。还好微软给我们提供了两个很不错的工具,windbg 和 IIS Diagnostics,winddbg是用来调试内存的工具,而IIS Diagnostics则是抓取内存的好工具,我也正是借助这两个工具才快速定位到了问题,不过很遗憾的是我抓取的dump文件由于时间太久,竟然找不到了,所以现在暂时无法一展它们的风采。(不过后续会介绍windbg的用法,因为它真的帮了我大忙)

    那到底是哪里引发的死锁呢,废话不多说,看看下面的代码就知道了,Discuz.Cache.DNTCache.cs 类文件

      1     /// <summary>

     2         /// 构造函数
     3         /// </summary>
     4         private DNTCache()
     5         {
     6             if(MemCachedConfigs.GetConfig() != null && MemCachedConfigs.GetConfig().ApplyMemCached)
     7                 applyMemCached = true;
     8 
     9             if (applyMemCached)
    10                 cs = new MemCachedStrategy();
    11             else
    12             {
    13                 cs = new DefaultCacheStrategy();
    14 
    15                 objectXmlMap = rootXml.CreateElement("Cache");
    16                 //建立内部XML文档.
    17                 rootXml.AppendChild(objectXmlMap);
    18 
    19                 //LogVisitor clv = new CacheLogVisitor();
    20                 //cs.Accept(clv);
    21 
    22                 cacheConfigTimer.AutoReset = true;
    23                 cacheConfigTimer.Enabled = true;
    24                 cacheConfigTimer.Elapsed += new System.Timers.ElapsedEventHandler(Timer_Elapsed); // 重点看下这个方法
    25                 cacheConfigTimer.Start();
    26             }
    27         }

    看下这个方法 Timer_Elapsed 

    1     private static void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    2         {
    3             if (!applyMemCached)
    4             {
    5                 //检查并移除相应的缓存项
    6                 instance = CachesFileMonitor.CheckAndRemoveCache(instance); // 这个方法里持有一个锁
    7             }

    8         } 

    看看这个方法 CachesFileMonitor.CheckAndRemoveCache 

      1     /// <summary>

     2         /// 检查和移除缓存
     3         /// </summary>
     4         /// <param name="instance"></param>
     5         /// <returns></returns>
     6         public static DNTCache CheckAndRemoveCache(DNTCache instance)//
     7         {
     8             //当程序运行中cache.config发生变化时则对缓存对象做删除的操作
     9             cachefilenewchange = System.IO.File.GetLastWriteTime(path);
    10             if (cachefileoldchange != cachefilenewchange)
    11             {                
    12                 lock (cachelockHelper)
    13                 {
    14                     if (cachefileoldchange != cachefilenewchange)
    15                     {
    16                         //当有要清除的项时
    17                         DataSet dsSrc = new DataSet();
    18                         dsSrc.ReadXml(path);
    19                         foreach (DataRow dr in dsSrc.Tables[0].Rows)
    20                         {
    21                             if (dr["xpath"].ToString().Trim() != "")
    22                             {
    23                                 DateTime removedatetime = DateTime.Now;
    24                                 try
    25                                 {
    26                                     removedatetime = Convert.ToDateTime(dr["removedatetime"].ToString().Trim());
    27                                 }
    28                                 catch
    29                                 {
    30                                     ;
    31                                 }
    32 
    33                                 if (removedatetime > cachefilenewchange.AddSeconds(-2))
    34                                 {
    35                                     string xpath = dr["xpath"].ToString().Trim();
    36                                     instance.RemoveObject(xpath, false); // 这个方法里持有第二个锁
    37                                 }
    38                             }
    39                         }
    40 
    41                         cachefileoldchange = cachefilenewchange;
    42 
    43                         dsSrc.Dispose();
    44                     }
    45                 }
    46             }
    47             return instance;
    48         }

    看看 

    RemoveObject 方法:
     1     /// <summary>
     2         /// 通过指定的路径删除缓存中的对象
     3         /// </summary>
     4         /// <param name="xpath">分级对象的路径</param>
     5         /// <param name="writeconfig">是否写入文件</param>
     6         public virtual void RemoveObject(string xpath, bool writeconfig)
     7         {
     8             lock (lockHelper)
     9             {
    10                 try
    11                 {
    12                     if (applyMemCached)
    13                     {
    14                         //移除相应的缓存项
    15                         cs.RemoveObject(xpath);
    16                     }
    17                     else
    18                     {
    19                         if (writeconfig)
    20                         {
    21                             CachesFileMonitor.UpdateCacheItem(xpath); // 这里再次持有锁
    22                         }
    23 
    24                         XmlNode result = objectXmlMap.SelectSingleNode(PrepareXpath(xpath));
    25                         //检查路径是否指向一个组或一个被缓存的实例元素
    26                         if (result.HasChildNodes)
    27                         {
    28                             //删除所有对象和子结点的信息
    29                             XmlNodeList objects = result.SelectNodes("*[@objectId]");
    30                             string objectId = "";
    31                             foreach (XmlNode node in objects)
    32                             {
    33                                 objectId = node.Attributes["objectId"].Value;
    34                                 node.ParentNode.RemoveChild(node);
    35                                 //删除对象
    36                                 cs.RemoveObject(objectId);
    37                             }
    38                         }
    39                         else
    40                         {
    41                             //删除元素结点和相关的对象
    42                             string objectId = result.Attributes["objectId"].Value;
    43                             result.ParentNode.RemoveChild(result);
    44                             cs.RemoveObject(objectId);
    45                         }
    46                     }
    47                     
    48                 }
    49                 catch//如出错误表明当前路径不存在
    50                 {}
    51 
    52             }

    53         } 

    再来看看方法UpdateCacheItem:

     1     /// <summary>
     2         /// 更新或插入相应的缓存路径
     3         /// </summary>
     4         /// <param name="xpath"></param>
     5         public static void UpdateCacheItem(string xpath)
     6         {
     7             DataTable dt = new DataTable("cachetableremove");
     8             dt.Columns.Add("xpath", System.Type.GetType("System.String"));
     9             dt.Columns.Add("removedatetime", System.Type.GetType("System.DateTime"));
    10 
    11             //当有要清除的项时
    12             DataSet dsSrc = new DataSet();
    13             lock (cachelockHelper)
    14             {
    15                 dsSrc.ReadXml(path);
    16 
    17                 bool nohasone = true;
    18                 foreach (DataRow dr in dsSrc.Tables[0].Rows)
    19                 {
    20                     if (dr["xpath"].ToString().Trim() == xpath)
    21                     {
    22                         dr["removedatetime"= DateTime.Now.ToString();
    23                         nohasone = false;
    24                         break;
    25                     }
    26                 }
    27 
    28                 if (nohasone)
    29                 {
    30                     DataRow dr = dsSrc.Tables[0].NewRow();
    31                     dr["xpath"= xpath;
    32                     dr["removedatetime"= DateTime.Now.ToString();
    33                     dsSrc.Tables[0].Rows.Add(dr);
    34                 }
    35 
    36                 dsSrc.WriteXml(path);
    37                 dsSrc.Dispose();
    38             }

    39         }

    通过上面的代码的红字体部分我们可以看到,如果DNTCache 启动它的定时器,它将会顺序持有如下锁

    cachelockHelper —— 》 CachesFileMonitor.CheckAndRemoveCache() 持有

        |

        |

    lockHelper  ——》 instance.RemoveObject()持有

        |

        |

    cachelockHelper  ——》 CachesFileMonitor.UpdateCacheItem() 持有

    如果刚好有一种情况持有所的顺序跟上面相反,比如持有顺序 lockHelper —— cachelockHelper —— lockHelper  ,而且这两种情况同时发生了,那死锁就这样产生了,那有没有这样的情况?有!

    我们来看看 Discuz.Cache.DNTCache.cs 的 GetCacheService():

     1     /// <summary>
     2         /// 单体模式返回当前类的实例
     3         /// </summary>
     4         /// <returns></returns>
     5         public static DNTCache GetCacheService()
     6         {
     7             if (instance == null)
     8             {
     9                 lock (lockHelper)
    10                 {
    11                     if (instance == null)
    12                     {
    13                         instance = applyMemCached ? new DNTCache() : CachesFileMonitor.CheckAndRemoveCache(new DNTCache());
    14                     }
    15                 }
    16             }
    17 
    18             return instance;

    19         } 

    看上面的 lock (lockHelper), 是不是很眼熟啊,对了,他刚好是上面第一种持有锁情况里面出现的第二个锁,只要这个 

    Discuz.Cache.DNTCache.
    GetCacheService() 和 CachesFileMonitor.CheckAndRemoveCache() 同时被启动,那死锁就产生了,而
    Discuz.Cache.DNTCache.
    GetCacheService()是返回当前缓存的实例,可以说他时时刻刻都在被调用,你可以尝试搜索一下
    Discuz.Cache.DNTCache.
    GetCacheService(),你会发现他无处不在,当 
    Discuz.Cache.DNTCache.
    GetCacheService()Discuz.Cache.DNTCache.Timer_Elapsed() 同时发生,死锁也就产生了。

    既然问题找到了,那该如何解决呢,我看了一下,这个

    Discuz.Cache.DNTCache里面用到的lock作用就是为了保证唯一性,但是我发现若不是唯一好像也没什么影响,于是我把lock注释了,试运行一段时间之后,发现并没有什么影响,于是一直沿用至今。

    本篇是本系列里针对DiscuzNT的c#代码做出优化的第一篇文章,比较遗憾的是第一大功臣windbg未能华丽登场,不过它以后还有机会。欲知windbg是如何登场的,敬请期待下回分解。



        
  • 相关阅读:
    lucene4 Filter
    lucene Query
    MyEclipse 中各种 libraries 的含义
    CRF++使用小结
    链表的输入与输出

    数据结构队列的各种操作
    设置背景颜色
    JavaScript由单价、数量计算总价
    中文和拼音自动转换的输入框
  • 原文地址:https://www.cnblogs.com/gezifeiyang/p/2079069.html
Copyright © 2011-2022 走看看