控製並發訪問的三道屏障: WCF限流(Throttling)體係探秘[下篇]
通過《上篇》介紹,我們知道了如何通過編程和配置的方式設置相應的最大並發量,從而指導WCF的限流體係按照你設定的值對並發的服務調用請求進行限流控製。那麼,在WCF框架體係內部,整個過程是如何實現的呢?這就是本篇文章需要為你講述的內容。實際上,整個限流控製體係,主要涉及到兩個對象:信道分發器(ChannelDispatcher)和ServiceThrottle。
一、信道分發器(ChannelDispatcher)與ServiceThrottle
從服務端整個消息監聽、接收、分發和處理框架體係角度來講,限流控製現在在信道分發器上。關於信道分發器在整個WCF服務端框架體係中所處的位置,由於在《WCF技術剖析(卷1)》的第2章和第7章均有過詳細的介紹,在這裏我隻作一些概括性的介紹。
在服務寄宿的時候,我們基於服務類型創建相應的ServiceHost對象,並為之添加一到多個終結點。在開始ServiceHost的時候,整個服務端消息處理體係會被建立,而整個體係的核心由兩個主要分發器(Dispatcher)構成,即信道分發器和終結點分發器。
WCF根據ServiceHost實際采用的監聽地址(不一定是終結點地址)創建相應的信道分發器,也就是說,ServiceHost包含的信道分發器的數量和監聽地址的數量相同。每個信道監聽器具有各自的信道監聽器,它們綁定到各自的監聽地址進行請求消息的監聽。
而終結點分發器與ServiceHost的終結點一一匹配,實際上可以看成是運行時的終結點。信道監聽器通過創建的信道棧將接收到的消息遞交給自己所在的信道分發器。信道分發器則通過消息承載的尋址信息將消息分發給相應的終結點分發器進行進一步處理。
舉個例子,假設我們現在對一個服務進行寄宿,並采用如下所示的配置。該服務具有三個基於NetTcpBinding的終結點,它們的終結點地址對應的端口分別為7777,8888和9999。而對於第一個終結點,我們將監聽地址設置成與第二個終結點的地址一樣。
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <services>
5: <service name="Artech.ThrottlingDemo.Service.CalculatorService">
6: <endpoint address="net.tcp://127.0.0.1:7777/calculatorservice" binding="netTcpBinding" contract="Artech.ThrottlingDemo.Service.Interface.ICalculator" listenUri="net.tcp://127.0.0.1:8888/calculatorservice"/>
7: <endpoint address="net.tcp://127.0.0.1:8888/calculatorservice" binding="netTcpBinding" contract="Artech.ThrottlingDemo.Service.Interface.ICalculator" />
8: <endpoint address="net.tcp://127.0.0.1:9999/calculatorservice" binding="netTcpBinding" contract="Artech.ThrottlingDemo.Service.Interface.ICalculator" />
9: </service>
10: </services>
11: </system.serviceModel>
12: </configuration>
如果我們創建了基於服務類型CalculatorService的ServiceHost,並成功開啟它,雖然該ServiceHost具有三個終結點,由於前兩個共享相同的監聽地址,所以實際采用的監聽地址隻有兩個,即net.tcp://127.0.0.1:8888/calculatorservice和net.tcp://127.0.0.1:9999/calculatorservice。WCF會創建兩個信道分發器,它們各自具有自己的信道監聽器,上述的兩個URI即為監聽器對應的監聽地址。此外,對應於ServiceHost的三個終結點,WCF會創建相應的終結點分發器。ServiceHost、信道分發器和終結點分發器之間的關係如圖1所示。
圖1 ServiceHost、信道分發器和終結點分發器之間的關係
而流量的控製就是實現在信道分發器上,也就是說當信道分發器將接收到的消息分發給相應的終結點分發器之前,就會進行流量的檢測。至於實現流量控製的原理,我們會在後麵討論。在這裏我們需要知道,WCF將所有限流相關的實現定義在ServiceThrottle類中。我們不妨來看看ServiceThrottle的定義。
1: public sealed class ServiceThrottle
2: {
3: //其他成員
4: public int MaxConcurrentCalls { get; set; }
5: public int MaxConcurrentInstances { get; set; }
6: public int MaxConcurrentSessions { get; set; }
7: }
由於具體的限流邏輯實現在ServiceThrottle的內部,並沒有通過公共方法的形式暴露出來(WCF甚至為ServiceThrottle定義了內部構造函數,我們不同直接通過new操作符創建ServiceThrottle對象),可見的隻是三個我們熟悉的最大並發量。
如果我們查看ChannelDispatcher的成員列表,可以看到類型為ServiceThrottle的ServiceThrottle屬性定義在ChannelDispatcher之中。當ChannelDispatcher進行消息分發之前對限流的控製就是通過該屬性表示的ServiceThrottle對象實現的。
1: public class ChannelDispatcher : ChannelDispatcherBase
2: {
3: // 其他成員
4: public ServiceThrottle ServiceThrottle { get; set; }
5: }
實際上服務行為ServiceThrottlingBehavior對限流控製的現實就是就是根據自身的設置(三個最大並發量)為信道分發器定義定製相應的ServiceThrottle。由於服務行為是針對服務級別的,即基於ServiceHost的,如果一個ServiceHost具有若幹個信道分發器,ServiceThrottlingBehavior會為每一個信道分發器進行相同的設置。ServiceThrottlingBehavior對信道並發器ServiceThrottle的設置實現在ApplyDispatchBehavior方法中,大概得邏輯如下麵的偽代碼所示:
1: public class ServiceThrottlingBehavior : IServiceBehavior
2: {
3: //其他成員
4: void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
5: {
6: ServiceThrottle serviceThrottle = new ServiceThrottle(serviceHostBase)
7: serviceThrottle.MaxConcurrentCalls = this.MaxConcurrentCalls;
8: serviceThrottle.MaxConcurrentSessions = this.MaxConcurrentSessions;
9: serviceThrottle.MaxConcurrentInstances = this.MaxConcurrentInstances;
10: foreach(ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
11: {
12: channelDispatcher.ServiceThrottle = serviceThrottle;
13: }
14: }
15: }
由於服務的限流控製最終是通過信道分發器的ServiceThrottle對象實現的,那麼我們可以通過信道分發器的ServiceThrottle的屬性,獲取到我們通過編程或配置方式設置的三個最大並發量的值。假設我們通過配置的方式為CalculatorService服務進行了如下的限流設置。
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <behaviors>
5: <serviceBehaviors>
6: <behavior name="throttlingBehavior">
7: <serviceThrottling maxConcurrentCalls="50" maxConcurrentInstances="30" maxConcurrentSessions="20"/>
8: </behavior>
9: </serviceBehaviors>
10: </behaviors>
11: <services>
12: <service name="Artech.ThrottlingDemo.Service.CalculatorService" behaviorConfiguration="throttlingBehavior">
13: <endpoint address="net.tcp://127.0.0.1:8888/calculatorservice" binding="netTcpBinding" contract="Artech.ThrottlingDemo.Service.Interface.ICalculator" />
14: <endpoint address="net.tcp://127.0.0.1:9999/calculatorservice" binding="netTcpBinding" contract="Artech.ThrottlingDemo.Service.Interface.ICalculator" />
15: </service>
16: </services>
17: </system.serviceModel>
18: </configuration>
在寄宿過程中,我們可以通過如下的方式得到ServiceHost的每個信道分發器所有的ServiceThrottle對象,並將MaxConcurrentCalls、MaxConcurrentInstances和MaxConcurrentSessions三個最大並發量打印出來。
1: using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
2: {
3: host.Open();
4: for (int i = 0; i < host.ChannelDispatchers.Count; i++)
5: {
6: ChannelDispatcher channelDispatcher = (ChannelDispatcher)host.ChannelDispatchers[i];
7: ServiceThrottle serviceThrottle =channelDispatcher.ServiceThrottle;
8: Console.WriteLine("ChannelDispatcher {0}: MaxConcurrentCalls = {1};MaxConcurrentInstances = {2};MaxConcurrentSessions = {3}",
9: i + 1,serviceThrottle.MaxConcurrentCalls,serviceThrottle.MaxConcurrentInstances,serviceThrottle.MaxConcurrentSessions);
10: }
11: }
輸出結果:
ChannelDispatcher 1: MaxConcurrentCalls = 50;MaxConcurrentInstances = 30;MaxConc
urrentSessions = 20
ChannelDispatcher 2: MaxConcurrentCalls = 50;MaxConcurrentInstances = 30;MaxConc
urrentSessions = 20
二、 ServiceThrottle對限流實現原理揭秘
WCF對限流控製的實現原理,相對來說還是比較複雜的。由於涉及到很多的內部對象,要將限流控製機製具體的實現將清楚,也是一件不太容易的事情。接下來,我盡量用比較直白的描述簡單地介紹一下WCF限流框架體係是如何將遞交處理的請求控製在我們設置的範圍的。無論是基於對並發會話的控製,還是對並發調用以及並發實例上下文的控製,都是采用相同的實現機製。WCF為此專門設計了一個內部組建,我們可以將其稱為流量限製器(FlowThrottle)。
流量限製器的設計大體上如圖1所示。首先,它具有一個最大容量屬性,表示最大流量;其內部維護一個隊列和一個計數器,次隊列被稱為等待隊列。當流量限製器初始化的時候,最大容量會被指定,等待隊列為空,計數器置為零。當需要處理需要進行流量控製的請求的時候,調用者將請求遞交給該流量限製器。流量限製器判斷當前的計數器是否大於最大容量,如果沒有則將其遞交到相應的處理組建進行處理,與此同時計數器加1。如果計數器超出最大容量,則將請求放到等待隊列中。如果之前的處理被正常處理,流量限製器的計數器會減1,如果此時等待隊列不會空,則會提取第一個請求進行處理。
圖2 流量限製器設計
由於WCF的限流通過三個指標來控製,即最大並發請求、最大並發實例上下文和最大並發會話,所以ServiceThtottle內部會維護三個不同的流量限製器。這個三個流量限製器的最大容量就是我們通過ServiceThrottlingBehavior設置的三個最大並發量屬性:MaxConcurrentCalls、MaxConcurrentInstances和MaxConcurrentSessions。圖3揭示了信道分發器、ServiceThtottle和流量限製器之間的關係。
圖3 ChannelDispatcher、ServiceThrottle和FlowThrottle之間的關係
ServiceThrottle三個流量限製器就像是設置在信道分發器三道閘門。當信道監聽器監測到請求消息,並創建信道棧接受消息,最後由信道監聽器分發給相應的終結點分發器,必須經過這三道閘門。如果一道閘門不放行,將不能再進行後續的處理,必須等到之前的操作結束使並發的操作小於閘門限製的容量。
從整個消息接收、處理的流程來看,第一道閘門是限製並發會話的流量限製器。當信道監聽器監聽到抵達的詳細請求後,創建信道棧對消息進行接收。如果創建的信道是會話信道,並發會話流量限製器會參與進來。並發會話流量限製器內部維護著一個會話信道計數器,如果該計數器超過通過通過ServiceThrottlingBehavior的MaxConcurrentSessions屬性設置的最大並發量,如果沒有繼續處理,否則將請求添加到自己的等待隊列中。關於會話信道,可以參閱《WCF技術剖析(卷1)》第9章關於會話的內容。
如果並發會話的流量限製器放行,對請求消息的處理進入第二道屏障,即並發調用流量限製器。原理與上麵相似,如果該流量限製器的並發請求數超出了通過ServiceThrottlingBehavior的MaxConcurrentCalls屬性設置的最大並發量,請求將會被添加到該自己的等待隊列中,否則繼續處理。
如果上麵兩個屏障順利通過,WCF會通過實例上下文提供器(InstanceContext Provider)獲取現有的或者創建新的實例上下文。此時,第三道屏障,即並發實例上下文流量控製器,開始發揮它的限流作用。與前麵的並發限流機製一樣,該流量限製器判斷自身維護的並發實例上下文計數器是否超過了通過ServiceThrottlingBehavior的MaxConcurrentInstances屬性設置的最大並發量,如果沒有則繼續處理,否則將請求添加到並發實例上下文流量控製器的等待隊列中。
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-27 15:04:36