最近在做一個MSMQ的Agent服務,在這里分享一下這個服務在優化的一點經驗,通過分析內存更准確地定位出程序中存在的性能問題,從而讓程序的性能以倍數的提升.
問題的引發
由於通過.NET MSMQ的Client實現消息分布和故障轉移實在測試效果並不理想..所以決定實現一個MSMQ的Agent服務,由於有網絡編寫的經驗所以對實現的效果還有很有信心的.可惜最終實現出來的效果實在慘不忍睹...4個連接並發消息寫入只有150/秒,實在是完全坑爹的結果!在架構上的設計並不存在問題,所以問題一定存在程序實現過程中,以往的經驗告訴自己做內存分析是最直接的辦法.
問題排查一Buffer沒正常回收到Pool
由於在測試過程CPU使用率並不高,所以懷疑是Buffer沒有回收到Pool導致
從分析的結果來說,的確是自己在寫代碼的時候犯錯了...存在大量的buffer被Pop出來導致大量內存被創建.但從代碼流程上來看找不到任何原因.
public override void MessageWrite(IMessage msg, BufferWriter writer) { HttpData hb = msg as HttpData; try { using (hb.Message) { msg.Save(writer); } } finally { if (hb != null) { hb.Message = null; HttpDataPackage.HttpDataPool.Push(hb); } } }
分析結果已經說明了問題所在,所以調試了一下程序,發現問題的根源是HttpData的Message為空,但using不會報錯坑爹啊...
public void ToProtocolData(HttpData httpbase) { httpbase.Action = Action; httpbase.Message = this; OnToProtocolData(httpbase); ; }
適當地修改一處程序問題解決.
吐能力提高但CPU占用資源過高
經過上面程序的修復4連接的秒吞吐能力由原來每秒150左右,上升到每秒2100/秒.得到的效果是非常的明顯的,但總的來說CPU占用資源還有點過高,為了驗證上一次修復的問題又進行了一次內存分析,分析的結果如下:
分析說明了一個問題由於Assembly.GetName()導致了大量的string創建,代碼如下:
value = string.Format("{0},{1}={2}", type.FullName, type.Assembly.GetName().Name, JsonConvert.SerializeObject(Message));
適當地修改一下
value = string.Format("{0}={1}", GetTypeName(type), JsonConvert.SerializeObject(Message)); private static System.Collections.Hashtable mNames = new System.Collections.Hashtable(1024); private static string GetTypeName(Type type) { string name = (string)mNames[type]; if (name == null) { lock (mNames) { name = (string)mNames[type]; if (name == null) { name = string.Format("{0},{1}", type.FullName, type.Assembly.GetName().Name); mNames[type] = name; } } } return name; }
經過以上的進一步優化后,服務的效果算是比較理想,完全滿足了現階段的需要.
意外的發現
從測試分析的結果看來,Newtonsoft.Json這個組件還有針對性優化的空間
針對以上分析,Newtonsoft.Json存在的問題應該如何優化,那就要留大家思考了.
總結
其實很多朋友喜歡通過CPU計時來看程序的快慢,但得到的結果只能說明問題,但對於如何解決這些問題,測試運行時間的結果似乎起不到什么作用.其實.NET程序有一個東西往往沒有得到關注,MS在一些文檔中要強調我們沒有必要關心,這個東西其實就是GC!的確我們對GC的工作直接可控性是沒有,但有一點可以肯定的就是GC的工作由對象的創建導致,如果想控制GC我們最好是從設計層面上控制對象的創建,這是最直接有效的辦法.