优化你的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是如何登場的,敬請期待下回分解。
?
? ???
總結
以上是生活随笔為你收集整理的优化你的DiscuzNT3.0,让它跑起来(4)asp.net 缓存和死锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 复制excel的表格怎么保持原来的大小(
- 下一篇: 回购注销股票是利好还是利空 股票回购注销