WCF優化的幾個常規思路


前幾天用WCF做項目時發現了一個效率問題,由於系統對效率要求較高,困擾了很長時間終於將問題解決了,寫下來為以后的兄弟們參考,第一次寫博客有不准確的地方還望同行們多噴多指點,先行謝過啦...

問題場景是這樣的,我上傳很多數據到服務器端,測試用的是100萬條,由於服務器端需要對數據篩選並過濾,那么就將數據駐留在內存中,在處理完以后才寫入到文件,需要的時候再從文件中讀取出來,當然向文件寫入和讀取都是單獨的線程來並發執行的...就是這樣看似誰都不影響誰的完美策略竟然導致后續Server端接收WCF請求的延遲,並不是服務方法的內部有多復雜,而是請求就不能立即進入響應方法中...困擾了三天之后也查閱了很多網上資料終於找到了原因,原因是我數據存到Server端以后因為某些需要又用了一個線程把數據挪到另外一個內存緩存中,按理說只是多占些內存而已不應該影響接收請求的速度啊,其實想來想去除了多占了很多內存以外還多開了好幾個線程,而且伴有大量的內存到文件的IO寫入寫出,就這樣造成了線程開啟的延遲,其實WCF的一次請求也是開啟一個線程響應的,這是我這次總結出來的,如果有不准確的地方希望兄弟們給指正,再次感謝

順便總結一下WCF效率優化設置的常規思路:


1、針對ServerHost端能接收的最大請求數量限制

在服務端設置ServiceHost,如:

1 host.Description.Behaviors.Add(new ServiceThrottlingBehavior() 
2 { 
3 MaxConcurrentInstances = 2000, 
4 MaxConcurrentCalls = 2000, 
5 MaxConcurrentSessions = 2000 
6 }); 

 

2、針對Server端服務對象的管理及並發方式

設置服務的InstanceContextMode 及ConcurrencyMode 特性,設置方法是在實現服務契約的類上面用如下特性:

1 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall,
2 ConcurrencyMode = ConcurrencyMode.Multiple, 
3 UseSynchronizationContext=false)]
4 public class MyService : IInterface{...}

 

說明:

(1)InstanceContextMode.PerCall表示每接收一個新的請求WCF都會新建一個服務實例來響應,這樣本質不同請求的響應就是並發執行的

(2)將ConcurrencyMode設置為ConcurrencyMode.Multiple也是為了保險起見,個人認為這個多線程訪問只有在InstanceContextMode.Single的時候也就是服務端針對每個請求都是一個對象響應的時候才談得上多線程並發訪問,因為InstanceContextMode.PerCall每個請求都是不同實例響應的,人家本來就是並發的

(3)如果不需要WCF服務內部幫你協調同步,那么設置UseSynchronizationContext=false,以后有多線程共享數據自己拿Lock去控制,我就是這么干的,因為我Lock的東西跟WCF沒啥關系

3、針對首次訪問速度很慢,以后訪問速度快的問題

如果是用的類似HttpBasicBinding,訪問服務端時首次很慢,可以考慮將binding的useDefaultWebProxy設置為"false",如果是用的類似NetTcpBinding,是沒有這個屬性的,這個屬性默認是true,意思是在wcf客戶端向Server端請求的時候總是嘗試先查找代理,找不到再去直接連接,如果您的服務端確定的話,干脆直接連接算了,不讓客戶端再去找代理,設置為false得了...

4、針對WCF串行處理造成的延遲

默認WCF的代理都是自動打開的,即使您將ConcurrencyMode設置為Multiple,WCF依舊會以串行的而不是並行的方式響應,因為選擇自動打開代理時,多個請求同時來到服務端,服務端會對請求進行排序等待且執行反序列化等,等這些都執行完了才會並發執行,這就造成了有先有后執行,既然是這樣我們就不要WCF自動打開代理,而是我們手動的顯示打開,具體方法如:((ICommunicationObject)client).Open();

(大家可以看看這個博客,我是在這里學到的:http://www.cnblogs.com/tyb1222/archive/2012/05/23/2515535.html)

5、WCF接收請求本質上也是開啟一個線程來響應client請求的,那么就涉及到windows線程池,一般線程池雖然可以通過ThreadPool.SetMinThreads方法設置最小的線程數量,但是系統內部其實對空閑線程過16s就清理一次的(即使你現在的活動線程沒有達到最小的線程池數量),只留一個空閑線程接收請求,如果剛剛清理完線程池而且這個空閑線程又剛好正接受請求的時候又來了一個新的請求,這時候CLR就會重新new一個線程,這時就會消耗一些時間,而且消耗時間的長短是時隨機的視系統情況而定(這個問題據說.net 4.0還有),為了防止這些情況發生就需要時刻提醒Windows多留出一個空閑的線程別被回收以備隨時調用,我們首先想到的就是定時提醒,但是如果每提醒一下就新建一個線程那么以后線程會越來越多cpu就不用干別的了,在那里只切換線程就夠它忙活一陣子了,好在系統有更好的機制叫完成端口(IOCP),這個東西大家可以去網上搜搜,說的都比我好多了,個人理解這種機制說白了就是有一個公共的消息隊列和幾個特定的線程,您cpu有幾核就會有幾個線程對應,它能保證空閑的cpu內核有空閑的線程,而且也不會沒有數量限制的新建線程,說白了就是您往完成端口發東西就會激活一個線程...因此我們要做的是就是定時往完成端口隊列中寫點東西,讓那個隊列去通知完成端口開辟出線程來,而且線程的回調方法什么都不做,說白了就是為了打開一個空閑的線程留給新的請求調用,節約時間,具體的實現方法如下(注:第5條觀點及代碼引自http://blogs.msdn.com/b/wenlong/archive/2010/02/11/why-does-wcf-become-slow-after-being-idle-for-15-seconds.aspx,建議大家去看看,雖然是英文的,寫的可比我好多了,哈):

 1 static ManualResetEvent s_dummyEvent;
 2 
 3 static RegisteredWaitHandle s_registeredWait;
 4 
 5 /// <summary>
 6 /// 函數功能:每隔0.5秒都向IO完成端口發送一個空的數據包,進而激活一個空閑的線程,
 7 /// 保證時刻有一個空閑的線程來響應,這樣wcf就避免請求因為新建線程延時了
 8 /// 備 注:防止.net線程池每16秒回收只留一個線程的情況發生
 9 /// </summary>
10 public static void DoWorkaround()
11 {
12 // Create an event that is never set
13 
14 //創建一個永遠都不啟動的事件
15 s_dummyEvent = new ManualResetEvent(false);
16 
17 // Register a wait for the event, with a periodic timeout. This causes callbacks
18 // to be queued to an IOCP thread, keeping it alive
19 
20 //注冊一個等待時間,每500ms注冊一次,這樣針對完成端口線程隊列的回調會保證完成端口線程的活躍(英文不好,大家忍下吧)
21 s_registeredWait = ThreadPool.RegisterWaitForSingleObject(
22 s_dummyEvent,
23 (a, b) =>
24 {
25 // Do nothing
26 },
27 null,
28 500,
29 false);
30 }

 

寫完上邊的這個方法在代碼中那里調用一下就行了...我修改了自己的程序結構以后那個請求延遲的問題沒有了,思來想去我的問題應該是大量的IO線程導致的...

 

以上就是我總結出來的幾點優化思路,希望能對其它兄弟有幫助,有不足的地方希望同行們多多指正


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM