控制并发访问的三道屏障: 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、流量限制器(FlowThrottle)

流量限制器的设计大体上如图1所示。首先,它具有一个最大容量属性,表示最大流量;其内部维护一个队列和一个计数器,次队列被称为等待队列。当流量限制器初始化的时候,最大容量会被指定,等待队列为空,计数器置为零。当需要处理需要进行流量控制的请求的时候,调用者将请求递交给该流量限制器。流量限制器判断当前的计数器是否大于最大容量,如果没有则将其递交到相应的处理组建进行处理,与此同时计数器加1。如果计数器超出最大容量,则将请求放到等待队列中。如果之前的处理被正常处理,流量限制器的计数器会减1,如果此时等待队列不会空,则会提取第一个请求进行处理。

图2  流量限制器设计

2、ServiceThrottle与流量限制器

由于WCF的限流通过三个指标来控制,即最大并发请求最大并发实例上下文最大并发会话,所以ServiceThtottle内部会维护三个不同的流量限制器。这个三个流量限制器的最大容量就是我们通过ServiceThrottlingBehavior设置的三个最大并发量属性:MaxConcurrentCalls、MaxConcurrentInstances和MaxConcurrentSessions。图3揭示了信道分发器、ServiceThtottle和流量限制器之间的关系。

图3 ChannelDispatcher、ServiceThrottle和FlowThrottle之间的关系

3、限流控制实现

ServiceThrottle三个流量限制器就像是设置在信道分发器三道闸门。当信道监听器监测到请求消息,并创建信道栈接受消息,最后由信道监听器分发给相应的终结点分发器,必须经过这三道闸门。如果一道闸门不放行,将不能再进行后续的处理,必须等到之前的操作结束使并发的操作小于闸门限制的容量。

从整个消息接收、处理的流程来看,第一道闸门是限制并发会话的流量限制器。当信道监听器监听到抵达的详细请求后,创建信道栈对消息进行接收。如果创建的信道是会话信道,并发会话流量限制器会参与进来。并发会话流量限制器内部维护着一个会话信道计数器,如果该计数器超过通过通过ServiceThrottlingBehavior的MaxConcurrentSessions属性设置的最大并发量,如果没有继续处理,否则将请求添加到自己的等待队列中。关于会话信道,可以参阅《WCF技术剖析(卷1)》第9章关于会话的内容。

如果并发会话的流量限制器放行,对请求消息的处理进入第二道屏障,即并发调用流量限制器。原理与上面相似,如果该流量限制器的并发请求数超出了通过ServiceThrottlingBehavior的MaxConcurrentCalls属性设置的最大并发量,请求将会被添加到该自己的等待队列中,否则继续处理。

如果上面两个屏障顺利通过,WCF会通过实例上下文提供器(InstanceContext
Provider)获取现有的或者创建新的实例上下文。此时,第三道屏障,即并发实例上下文流量控制器,开始发挥它的限流作用。与前面的并发限流机制一样,该流量限制器判断自身维护的并发实例上下文计数器是否超过了通过ServiceThrottlingBehavior的MaxConcurrentInstances属性设置的最大并发量,如果没有则继续处理,否则将请求添加到并发实例上下文流量控制器的等待队列中。

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接

时间: 2024-08-02 17:32:09

控制并发访问的三道屏障: WCF限流(Throttling)体系探秘[下篇]的相关文章

控制并发访问的三道屏障: WCF限流(Throttling)体系探秘[上篇]

WCF是一个基于多线程的消息监听.接收和处理框架体系,能够同时应付来自相同或者不同客户端的服务调用请求,并提供完善的同步机制确保状态的一致性.一方面,我们期望WCF服务端能够处理尽可能多的并发请求,但是资源的有限性决定了并发量有一个最大值.如果WCF不控制进入消息处理系统的并发量,试图处理所有抵达的并发请求,一旦超过了这个临界值,整个服务端将会由于资源耗尽而崩溃. 所以,我们需要在WCF的消息接收系统和消息处理系统之间设置一道道屏障,将流入消息处理系统的请求控制到一个最佳的范围,以实现对现有资源

线程同步工具(一)控制并发访问资源

声明:本文是< Java 7 Concurrency Cookbook>的第三章, 作者: Javier Fernández González 译者:郑玉婷     控制并发访问资源 这个指南,你将学习怎样使用Java语言提供的Semaphore机制.Semaphore是一个控制访问多个共享资源的计数器. Semaphore的内容是由Edsger Dijkstra引入并在 THEOS操作系统上第一次使用. 当一个线程想要访问某个共享资源,首先,它必须获得semaphore.如果semaphor

线程同步工具(二)控制并发访问多个资源

声明:本文是< Java 7 Concurrency Cookbook>的第三章, 作者: Javier Fernández González 译者:郑玉婷 控制并发访问多个资源 在并发访问资源的控制中,你学习了信号量(semaphores)的基本知识. 在上个指南,你实现了使用binary semaphores的例子.那种semaphores是用来保护访问一个共享资源的,或者说一个代码片段每次只能被一个线程执行.但是semaphores也可以用来保护多个资源的副本,也就是说当你有一个代码片段

《C#并发编程经典实例》—— 用限流和抽样抑制事件流

声明:本文是<C#并发编程经典实例>的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文. 问题 有时事件来得太快,这是编写响应式代码时经常碰到的问题.一个速度太快的事件流可导 致程序的处理过程崩溃. 解决方案 Rx 专门提供了几个操作符,用来对付大量涌现的事件数据.Throttle 和 Sample 这两个操 作符提供了两种不同方法来抑制快速涌来的输入事件. Throttle 建立了一个超时窗口,超时期限可以设置.当一个事件到达时,它就重新开始计 时.当超时期限到达时,它就把窗口

WCF中并发(Concurrency)与限流(Throttling)体系深入解析系列[共7篇]

服务(Service)的本质就是提供服务消费者期望的某种功能,服务的价值体现在两个方面:服务本身的质量和寄宿服务的平台应付消费者的数量,并发(Concurrency)的关注的是第二个要素.WCF服务寄宿于资源有限的环境中,要实现服务效用的最大化,需要考虑如何利用现有的资源实现最大的吞吐量(Throughput).提高吞吐量就某个寄宿的服务实例(Service Instance)来说,一个重要的途径就是让它能够同时处理来自各个客户端(服务代理)的并发访问.WCF实现了一套完整的并发控制体系,为你提

聊聊高并发系统之限流特技-2

接入层限流 接入层通常指请求流量的入口,该层的主要目的有:负载均衡.非法请求过滤.请求聚合.缓存.降级.限流.A/B测试.服务质量监控等等,可以参考笔者写的<使用Nginx+Lua(OpenResty)开发高性能Web应用>. 对于Nginx接入层限流可以使用Nginx自带了两个模块:连接数限流模块ngx_http_limit_conn_module和漏桶算法实现的请求限流模块ngx_http_limit_req_module.还可以使用OpenResty提供的Lua限流模块lua-resty

高并发系统之限流特技:有了它,京东6.18如虎添翼!

在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流.缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹:而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开:而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀.抢购).写服务(如评论.下单).频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流. 限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到

基于Redis的限流系统的设计

基于Redis的限流系统的设计,主要会谈及限流系统中限流策略这个功能的设计:在实现方面,算法使用的是令牌桶算法来,访问Redis使用lua脚本. 1.概念 限流是对系统的出入流量进行控制,防止大流量出入,导致资源不足,系统不稳定. 限流系统是对资源访问的控制组件,控制主要的两个功能:限流策略和熔断策略,对于熔断策略,不同的系统有不同的熔断策略诉求,有的系统希望直接拒绝.有的系统希望排队等待.有的系统希望服务降级.有的系统会定制自己的熔断策略,很难一一列举,所以本文只针对限流策略这个功能做详细的设

[原创]WCF后续之旅(12): 线程关联性(Thread Affinity)对WCF并发访问的影响

在本系列的上一篇文章中,我们重点讨论了线程关联性对service和callback的操作执行的影响:在service host的时候,可以设置当前线程的SynchronizationContext,那么在默认情况下,service操作的执行将在该SynchronizationContext下执行(也就将service操作包装成delegate传入SynchronizationContext的Send或者Post方法):同理,对于Duplex同行方式来讲,在client调用service之前,如果