除了cpu外其他屬性均正常
在性能監視器裏添加Asp.net 的Current Request(當前請求數),發現有一些請求始終沒有結束,對比cpu的使用率,可以看到當有一個這樣的請求,cpu爲25%兩個則爲50%,三個75%....
看到這個問題,我的第一個反應是請求爲什麼不過期,而是一直佔用cpu,第二個,奇怪的地方,是cpu增長的方式和幅度很有趣,固定每次增長25%,並且增長的時間不定.
開始我沒有什麼太好的想法,但是光cpu高,其他指標正常,讓我覺得起一定有某項工作在努力進行,而且不涉及大量內存和io操作,最簡單的模擬方法莫過於寫一個什麼都不做的死循環,於是我新建了一個web項目,在本機的的iis上建立一個站點指向他,爲了真實一些,我還給這個站點單獨設置了一個應用程序池,然後,我在一個空頁面的onload里加上while(true){}看看效果,下面是訪問這個空頁面的時候的cpu情況,我們可以看到,佔用率超過了50%
還有:對這個頁面的請求一直不超時,無論我回收甚至停止應用程序池,w3wp都執着的佔用我50%的cpu,而我是一個雙核處理器,而有問題的那臺是四核的.每次漲25%,呵呵,有點意思哈,是不是實際問題很像了呢?
那麼爲什麼請求不超時呢?本地的代碼web.config中設置爲debug="true",這時默認是不會超時的,但是發佈後一般會改爲debug="false",這時纔有請求超時,推測另一組的開發人員出於某種原因,發佈後沒有進行修改.所以造成了一直存在的請求.趕忙叫請人看了看,果然webconfig中debug="true"
但現在的問題是到底是什麼請求導致了這些超長時間請求呢?難道真是一個死循環嗎?
要解決這個問題,我們首先要找到有問題的請求,開始的時候我們不知道怎麼查看iis當前的具體請求,而iis只提供了當前請求數,但是起碼我們有iis的日誌吧,所以我想使用一個程序來監視cpu的值,一旦發生了25%的長期提升,我就記錄下發生的時間,然後到log裏面看看對應時間到底有些什麼請求,下面就是我的程序,使用的是程序操控performace counter,大概邏輯我會在下面講述
{
static PerformanceCounter pc;
public static void Test()
{
TimeSpan span=new TimeSpan(0,2,0);//cpu升高持續多長時間需要記錄:現爲2分鐘
int interval=1000;//兩次查看cpu使用率的間隔:現爲1秒
float limit=20f;//cpu升高百分比限制:現爲20%
List<TimeValueRecord> list = new List<TimeValueRecord>();
pc = new PerformanceCounter();
// 獲取或設置此性能計數器的性能計數器類別的名稱。
pc.CategoryName = "Processor";
// 獲取或設置與此 PerformanceCounter 實例關聯的性能計數器的名稱。
pc.CounterName = "% Processor Time";
pc.InstanceName = "_Total";
pc.ReadOnly = true;
float pre = 0f;
float cur = 0f;
bool firstTime = true;//排除第一次運行時的躍升干擾
DateTime currDate = DateTime.Now;
DateTime preDate = DateTime.Now;
float diff = 0f;
while (true)
{
cur = pc.NextValue();
if (!firstTime)
{
currDate = DateTime.Now;
diff = cur - pre;
if (diff > limit)
list.Add(new TimeValueRecord(preDate,pre));
foreach (TimeValueRecord entry in list)
{
if (cur - entry.Value < limit)
entry.Value = int.MaxValue;//foreach中不能remove,這樣就等於使得cur-entry.value爲一個負數,肯定小於limit,
else if (currDate - entry.Key > span)
{
Console.WriteLine("Jump Occurred at {0}", entry.Key);
entry.Value = int.MaxValue;//以免多次輸出
}
}
}
pre = cur;
preDate = currDate;
firstTime = false;
Thread.Sleep(interval);
}
}
}
internal class TimeValueRecord
{
public TimeValueRecord(DateTime key,float value)
{
Key = key;
Value = value;
}
public DateTime Key;
public float Value;
}
大概邏輯是每隔interval秒查看一次cpu的使用率,如果當前值和上次值之間的差距<25%(程序裏用的是20%,爲的是靈敏度高一些),就放在一個數組裏,注意由於有的時候服務器cpu會出現在一瞬間很高的情況,然後很快降下來的情況,因此,我們還需要知道這次提升是否持續了一段時間,程序裏是2分鐘,一旦持續超過兩分鐘,就把這次提升的時間輸出,這裏簡單的輸出在了控制檯裏
寫好之後在自己的機器上運行的不錯,完全可以監視自己故意製造的cpu使用率持久提升,但是麻煩的是,放在服務器上就沒效果了,大概是因爲在服務器上,cpu並不是如我想象的瞬間提升,而是有一個過程,我的兩次cpu監視的間隔設的是一秒,在這一秒內服務器的cpu沒有提高到20%,所以程序就沒有捕獲,所以只好把間隔調大一些,不過自然輸出時間的誤差就要大了一些.
不過這時我們的運維人員找到一個很好的工具,微軟的iis診斷工具,可以看到當前請求內容,在這裏我提供了一個下載連接,所以上面我的程序就沒有什麼用處了,不過可以作爲參考,改改的話可以監視很多性能指標的變動時間:)
很快,運維人員就找到了有問題的請求,是一個插入高亮代碼的頁面,裏面代碼特簡單,實在看不出有死循環,實際這個頁面前一天大家曾經懷疑過,但是就是因爲裏面代碼一幕瞭然,就放過了,我們來看看這個頁面的代碼
2 using System.Data;
3 using System.Configuration;
4 using System.Collections;
5 using System.Web;
6 using System.Web.Security;
7 using System.Web.UI;
8 using System.Web.UI.WebControls;
9 using System.Web.UI.WebControls.WebParts;
10 using System.Web.UI.HtmlControls;
11
12 public partial class InsertCode : System.Web.UI.Page
13 {
14 protected void Page_Load(object sender, EventArgs e)
15 {
16 if (Request["Language"] != null)
17 {
18 HighLightService s = new HighlightService();
19 bool outlining = true;
20 string outText;
21 if (string.IsNullOrEmpty(Request["outlining"]))
22 outlining = false;
23 if (Request["Code"] == null || Request["Language"] == null)
24 outText = s.Transform("public int c", "C#", outlining);
25 else
26 outText = s.Transform(Request["Code"], Request["Language"], outlining);
27 outText = outText.Replace("/"", "///"");
28 outText = outText.Replace("/n", "<br>");
29 outText = outText.Replace("/r", "<br>");
30 outText = outText.Replace("<pre>", "");
31 outText = outText.Replace("</pre>", "");
32 outText = Globals.ReplaceSpace(outText);
33 outText = @"<div style='BORDER-RIGHT: windowtext 0.5pt solid;PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px;PADDING-TOP: 4px; BORDER-LEFT: windowtext 0.5pt solid;WIDTH: 95%; BORDER-BOTTOM: windowtext 0.5pt solid;word-break:break-all'>" + outText + "</DIV>";
34 clientScript.Text = "var outText=/"" + outText + "/"; FCK.InsertHtml(outText);window.close();";
35 }
36 }
37 }
再仔細看看,代碼裏有三個方法調用(見加粗的地方那個),前兩個經檢查是webservice,webservice的站點也不在那臺服務器上,所以和cpu高沒有關係,其他語句都很簡單,不太可能有問題,而後面的一個方法調用很可能就是問題所在的地方了
打開這個方法看看,裏面是一個正則替換,也很簡單,看來所有的問題就在這個正則上了,讀不明白沒關係,我也沒讀明白:P
{
string findstr="(?<fore>(?:(?:[^< ])*(?:<(?:!--(?:(?:[^-])*(?:(?=-->)|-))*--|(?:[^>])+)>)?)*)[ ](?<back>(?:(?:[^< ])*(?:<(?:!--(?:(?:[^-])*(?:(?=-->)|-))*--|(?:[^>])+)>)?)*)";
string replacestr="${fore} ${back}";
string targetstr=System.Text.RegularExpressions.Regex.Replace(content,findstr,replacestr,System.Text.RegularExpressions.RegexOptions.IgnoreCase);
return targetstr;
}
顯然這個正則並不是所有的情況都有問題,不然後臺監視到的cpu佔用會厲害的多,所以我找了幾段文本去測試,經過反覆測試,發現匹配諸如<span>&dll;&dfasfd;&dsd;BB()</span>
這樣的代碼,會造成死循環(當然也可能是執行時間巨長無比)而在此期間cpu會被佔用,內存一切正常下圖是我用自己的工具執行匹配時的截圖
具體爲什麼,到底是什麼文字模式會導致問題,我還不知道,但是可以肯定的是,問題就在這裏了,具體怎麼解決,就交給那組的同事了
總結:
身爲程序員,通過自己的努力能解決一個問題,心裏都是很愉快的,不過高興之餘也應該有所總結,爲了自己不被同樣的問題再困擾,也爲了下次能夠更快的找到問題
webconfig中的debug屬性,發佈時要注意修改,當然保持debug=true,也能讓你查看到許多自己程序的問題,所以最好是最初發布時能夠把他打開
適應了調試環境,我們在又不能調試,又沒法記錄日誌的時候,需要能靜下心來看代碼.許多問題通過仔細的閱讀和分析代碼都能找到.
正則表達式,特別是相對複雜的表達式,一定不要忘記測試其有效性和性能,所以自己最好是手邊常備一個習慣的正則工具,我用的是自己寫的,下面這個blog上的正則工具比較全面,大家也可以用用看
http://www.cnblogs.com/Heroman/archive/2005/01/13/91265.html
多核服務器處理多線程時,我們可以看到線程和cpu數目之間的直接關係,但不等於說一個線程是由一個cpu全權處理的,具體調度問題我還不太清楚
上面那個正則哪裏有問題,我還不知道,大家如果能看出來一定要告訴我
關於排錯這篇文章講的還不錯
http://news.csdn.net/n/20071228/112244.html