服务(Service)的本质就是提供服务消费者期望的某种功能,服务的价值体现在两个方面:服务本身的质量和寄宿服务的平台应付消费者的数量,并发(Concurrency)的关注的是第二个要素。WCF服务寄宿于资源有限的环境中,要实现服务效用的最大化,需要考虑如何利用现有的资源实现最大的吞吐量(Throughput)。提高吞吐量就某个寄宿的服务实例(Service Instance)来说,一个重要的途径就是让它能够同时处理来自各个客户端(服务代理)的并发访问。WCF实现了一套完整的并发控制体系,为你提供了不同的并发模式。
我经常说软件架构是一门权衡的艺术,需要综合考虑各种相互矛盾的因素,找到一种最优的组合方式。提高单个服务实例允许的并发访问量能够提高整体吞吐量,这样的理论依赖于一种假设,那就是服务端所能使用的资源是无限。我们知道,这种假设无论在什么情况下都不会成立。如果我们并发量超出了服务端所能承受的临界点,整个服务端将会崩溃。所以,WCF一方面需要允许让单个服务实例并发处理接收到的多个请求,同时也需要设置一道闸门控制并发的数量。WCF的流量限制(Throttling)体系为你创建了这道闸门。
从本篇文章开始,我将发布一系列的文章对WCF并发架构体系进行深入剖析 ,先来看看并发的基本介绍。
并发的含义就是多个并行的操作同时作用于一个相同的资源或者对象,或者说同一个资源或者对象同时应付多个并行的请求。对于WCF的并发来说,这里将的“资源或者对象”指的就是承载服务操作最终执行的服务实例(Service Instance)。而WCF将服务实例封装在一个称为实例上下文(InstanceContext)对象中,所以WCF中的并发指的是同一个服务实例上下文同时处理多个服务调用请求。
WCF服务端框架一个主要的任务是将接收到的服务调用请求分发给激活的服务实例,调用相应的服务操作并返回执行结果。也就是说,服务操作的执行最终还是会落实到某个具体的服务实例上。《WCF技术剖析(卷1)》的第9章对WCF的实例化机制进行了深入的剖析,从中我们知道在WCF服务端框架体系中,激活的服务实例并不是单独存在的,而是被封装在一个被称为实例上下文(InstanceContext)对象中。WCF提供了三种不同的实例上下模式(Per-Call、Per-Session和Single)实现了不同的服务实例上下文提供机制。
所以,WCF并发框架体系解决的是如何有效地处理被分发到同一个服务实例上下文的多个服务调用请求,这些并行的调用请求可能来自不同的客户端(服务代理),也可能相同的客户端。WCF并发的本质上可以通过图1体现。
图1 通过一个InstanceContext对多个并发请求的处理
由于WCF的并发处理属于服务本身自身的行为,所以我们通过服务行为(Service Behavior)的形式对采取的并发策略进行控制,而不同的并发策略定义在相应的并发模式(Concurrency Mode)下面。
WCF为三种典型的并发处理策略定义了三种典型的并发模式,即Single、Reentrant和Multiple。这三种并发模式通过ConcurrencyMode的三个同名的枚举项表示,ConcurrencyMode定义如下:
1: public enum ConcurrencyMode
2: {
3: Single,
4: Reentrant,
5: Multiple
6: }
通过ConcurrencyMode枚举项表示的三种不同的并发模式体现了WCF处理并发请求的三种不同能策略:
并发模式的采用是服务单边的选择,是服务端个人的行为,所以并发模式以服务行为的方式定义,我们只需要在服务类型上应用ServiceBehaviorAttribute特性,为ConcurrencyMode属性设置相应的值即可,ServiceBehaviorAttribute定义如下:
1: [AttributeUsage(AttributeTargets.Class)]
2: public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior
3: {
4: //其它成员
5: public ConcurrencyMode ConcurrencyMode { get; set; }
6: }
如果显示指定服务采用的并发模式,默认使用的是ConcurrencyMode.Single,所以下面两种服务定义方式是等效的。
1: public class CalculatorService : ICalculator
2: {
3: //省略成员
4: }
5:
6: [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Single)]
7: public class CalculatorService : ICalculator
8: {
9: //省略成员
10: }
WCF并发解决的是同一个InstanceContext对象在处理并发请求是采用怎样的处理策略。我们知道InstanceContext不仅仅是封装真正服务实例的容器,当我们通过双向通信的机制从服务端回调客户端操作时,真正执行回调操作的回调对象也是封装在InstanceContext中。
在双向通信的场景中,如果多个服务端或者同一个客户端的多个并发的服务调用操作所指定的回调实例上下文(即封装回调操作的InstanceContext对象),就可能出现针对同一个InstanceContext的并发回调的现象。WCF采用与正常服务调用相同的机制来处理并发回调,实际上WCF采用几乎一样的机制来实现正常的服务调用和回调。
与通过将ServiceBehaviorAttribute特性应用到服务类型并指定采用的并发模式相类似,回调采用的并发模式通过应用在回调类型上的CallbackBehaviorAttribute特性来指定。CallbackBehaviorAttribute中同样定义了ConcurrencyMode属性:
1: [AttributeUsage(AttributeTargets.Class)]
2: public sealed class CallbackBehaviorAttribute : Attribute, IServiceBehavior
3: {
4: //其它成员
5: public ConcurrencyMode ConcurrencyMode { get; set; }
6: }
下面的代用中,我们通过在回调类型CalculatorCallbackService上应用CallbackBehaviorAttribute特性,将回调并发模式设置成ConcurrencyMode.Multiple。
1: [CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
2: public class CalculatorCallbackService:ICalculatorCallback
3: {
4: //省略成员
5: }
相信你还会记得在上面一章介绍事务编程(《上篇》、《中篇》、《下篇》)的时候,可以在服务类型上面应用ServiceBehaviorAttribute特性将ReleaseServiceInstanceOnTransactionComplete属性设成True,这样可以让WCF在事务结束之后将封装了服务实例的InstanceContext对象释放掉。不过这样的设置之后再并发模式为ConcurrencyMode.Single的前提下方才有效,否则在进行服务寄宿的时候将会抛出异常。
比如说,我们定了如下一个BankingService服务类型,通过ServiceBehaviorAttribute特性指定ReleaseServiceInstanceOnTransactionComplete为True,并采用ConcurrencyMode.Multiple并发模式。
1: [ServiceBehavior(ReleaseServiceInstanceOnTransactionComplete = true,ConcurrencyMode = ConcurrencyMode.Multiple)]
2: public class BankingService : IBankingService
3: {
4: [OperationBehavior(TransactionScopeRequired = true)]
5: public void Transfer(string accountFrom, string accountTo, decimal amount)
6: {
7: //省略实现
8: }
9: }
当我们试图寄宿该BankingService服务的时候,如图2所示的InvalidOperationException异常会被抛出,并提示对于已经将ReleaseServiceInstanceOnTransactionComplete设置成True的服务来说,必须将并发模式设成ConcurrencyMode.Multiple。
图2 在Multiple+ReleaseServiceInstanceOnTransactionComplete导致的异常
WCF提供的三种不同的并发模式,使开发者可以根据具体的情况选择不同的并发处理的策略。对于这三种并发模式,Multiple采用的并行的执行方式,而Single和Reentrant则是采用串行的执行方式。串行执行即同步执行,在WCF并发框架体系中,这样的同步机制是如何实现的呢?请关注下篇文章。