【转载】Programming WCF Services翻译笔记(七)

Programming WCF Services翻译笔记(七)

本书第四章介绍了关于实例管理的相关技术。“WCF支持三种实例激活的类型:单调服务(Per-Call Service)会为每次的客户端请求分配(销毁)一个新的服务实例。会话服务(Sessionful Service)则为每次客户端连接分配一个服务实例。最后一种是单例服务(Singleton Service),所有的客户端会为所有的连接和激活对象共享一个相同的服务实例。”

对于Per-Call Service的翻译,我踌躇良久,最后还是决定按照Singleton服务的翻译,将其译为单调服务,意即为每次调用创建一个服务实例,与单例服务相对应。不知是否妥当?其实最好的翻译就是保持原文不变,但对于整本书而言,如果保持英文术语,也有许多不便的地方。

实例模式的配置通过ServiceBehavior完成,以下是ServiceBehaviorAttribute的定义:
public enum InstanceContextMode
{
   PerCall,
   PerSession,
   Single
}
[AttributeUsage(AttributeTargets.Class)]
public sealed class ServiceBehaviorAttribute : Attribute,...
{
   public InstanceContextMode InstanceContextMode
   {get;set;}
   //More members
}

单调服务(Per-Call Service)

单调服务如图所示:
figure-4.1.gif
执行步骤如下:
1. 客户端调用代理,代理将调用转发给服务。
2. WCF创建一个服务实例,然后调用服务实例的方法。
3. 当方法调用返回时,如果对象实现了IDisposable接口,WCF将调用IDisposable.Dispose()方法。
4. 客户端调用代理,代理将调用转发给服务。
5. WCF创建一个对象,然后调用对象的方法。

单调服务的一个最重要优势在于它能够节省资源,支持系统的可伸缩性。由于服务实例的生命周期只存在于一次调用期间,特别对于那些持有昂贵资源的服务实例而言,这种方式可以有效地提高系统性能。而且,销毁服务实例时,WCF不会断开与客户端(通过客户端的代理)的连接,这比创建实例与连接所消耗的资源要少得多。

单调服务体现的优势在事务编程与队列服务中更为明显,它可以保证在事务编程中实例状态的同步;而对于队列断开调用而言,则单调服务能够建立服务实例与离散队列消息之间的简单映射。

单调服务的配置通过ServiceBehavior,如下所示:
[ServiceContract]
interface IMyContract
{...}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class MyService : IMyContract
{...}
注意,ServiceBehavior特性只能应用到类上面,这在前面介绍的特性定义可以看出。实际上,这也是合理的约束,因为如果将ServiceBehavior应用到接口上,由于接口是不能实例化的,自然会出现错误。

单调服务实例是状态相关的,但是由于实例会在调用之初创建,而在调用之后被销毁,因而这样的单调服务实例是无法保存状态的。为了解决这一问题,我们可以引入数据库或者文件保存状态,或者利用全局变量临时存储状态。那么为了获取状态,潜在的含义是单调服务的每个操作应该定义一个参数,用来传递状态或状态的ID。

书中有一句话非常重要:“如果单调服务真的与状态无关,就根本不需要单调激活模式。准确地讲,正是因为状态,特别是代价昂贵的状态,才需要使用单调模式。”很多时候,我们认为单调服务实例的生命周期存在于每次调用,因而想当然的认为这样的服务实例应该是无状态的。这样的操作可能只是简单执行某项任务,而不会对服务对象的属性进行操作。表面看起来确实如此,但在这里我们却忽略了我们采用单调服务的根本原因,是在于单调激活模式的本质就在于能够适时释放实例所持有的昂贵资源,这里的资源大体上讲就是一种状态。如果不需要维护状态,则以为着性能上没有太大的损耗,我们就没有必要采用单调激活模式了,毕竟频繁地创建与销毁实例,仍然会对性能造成一定的影响。

对于WCF服务而言,单调服务可以算是最佳的实例激活模式。书中介绍:“一个有力的论据是单调服务更利于系统的可伸缩性。为了更好的支持可伸缩性,服务设计有一个黄金法则是10X,即设计出的每个服务应该能够处理至少多于需求一个量级以上的负载。这是一个工程学准则,工程师在设计系统时,绝不能够“鼠目寸光”,只考虑当前指定负载的处理。如果一幢大楼,只能够支撑当前需求确定的承重,还会有人胆敢居住吗?如果一座电梯,只能够承受规定的六位乘客的重量,还会有人愿意乘坐吗?软件系统同样如此。为什么不能针对当前指定的负载进行系统设计?假设采用这样的设计方式,一旦系统的每位用户增加了业务量,那么系统就会变得岌岌可危。设计良好的系统必须能够经久不衰,经得起时间的考验。为了实现这一目的,就需要应用10X的黄金法则,有效地利用单调服务所能提供的可伸缩性。采用单调服务的另一个有力论据是关于事务的处理。正如第7章介绍的那样,事务绝对是每个系统所必需的,单调服务有利于实现事务编程模型,而不用考虑系统的负载。”

会话服务

从执行方式与激活方式来看,会话服务相当于.NET Remoting中的客户端激活模式。也就是为每个客户端创建一个专门的服务实例。只要会话没有结束,该实例就不会被销毁。

“客户端会话是一个代理对应一个服务终结点。如果客户端为相同或不同的终结点创建了另外的代理,则新建的代理就会与新的实例和会话建立关联。”根据这句话的内容,可以理解到对于会话服务而言,是一个客户端代理对应一个服务实例。也就是说,会话服务中的服务是与代理相对应的,而不是对应于一个客户端。这是它与.NET Remoting的客户端激活模式不同的地方。

此外,会话服务存在可伸缩性的问题。由于每个客户端都需要维护一个会话,如果存在多个独立的客户端,则创建专门的服务实例的代价太大。

配置会话服务的方式仍然是使用ServiceBehavior特性,如下所示:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
class MyService : IMyContract
{...}

然而,InstanceContextMode的默认值为InstanceContextMode.PerSession,如果没有设置InstanceContextMode,则服务默认为会话服务。

仅仅为服务配置InstanceContextMode是不够的,因为会话服务必须要求客户端维持一个会话,这就需要让客户端的WCF运行时知道服务是否使用了会话,因此,我们需要通过ServiceContract特性提供的SessionMode属性,设置服务契约。SessionMode的定义如下:
public enum SessionMode
{
   Allowed,
   Required,
   NotAllowed
}

“SessionMode的默认值为SessionMode.Allowed。当客户端导入契约元数据时,服务元数据将包含SessionMode值,并会如实地反映它的内容。”

如果服务的SessionMode被配置为SessionMode.Allowed,并不必然代表服务为会话服务。以下是对各种情况的说明:
1、 如果服务被配置为单调服务,则服务与SessionMode无关;
2、 如果服务被配置为会话服务,且SessionMode为Allowed,则:
(1)如果服务使用的绑定为BasicHttpBinding,服务为单调服务;
(2)如果服务使用的绑定为没有包含安全与可靠消息传输的WSHttpBinding绑定,服务为单调服务;
(3)如果服务使用的WSHttpBinding绑定包含了安全(为默认配置)或者可靠的消息传输,或者使用NetTcpBinding绑定、NetNamedPipeBinding绑定,服务为会话服务。

当SessionMode为Required时,服务不能使用BasicHttpBinding绑定或者没有包含安全与可靠消息传输的WSHttpBinding绑定,在装载服务时会对此进行验证。作者建议,“若要设计一个会话契约,我主张使用SessionMode.Required,而非SessionMode.Allowed默认值。”

如果SessionMode为NotAllowed,则不管服务配置如何,它总是采用单调服务方式。但如果契约使用了NetTcpBinding或NetNamedPipeBinding绑定,则不能将服务的SessionMode配置为NotAllowed。作者的建议是“是在选择使用SessionMode.NotAllowed的同时,总是将服务配置为单调服务”。

应该避免将单调服务与会话契约混合定义在相同的会话服务类型中,即使WCF允许这样的配置:
[ServiceContract(SessionMode = SessionMode.Required)]
interface IMyContract
{...}

[ServiceContract(SessionMode = SessionMode.NotAllowed)]
interface IMyOtherContract
{...}

//Avoid
class MyService : IMyContract,IMyOtherContract
{...}

会话应该保证是可靠的,一个实现了会话契约的服务,它包含的所有终结点所公开的契约都应该使用支持可靠传输会话的绑定。

“通常,一旦客户端关闭了代理,会话就会终止。但是,客户端也可以强行终止会话,也可能因为通信故障而终止会话。每个会话还包含了一个空闲超时值,默认为10分钟。如果客户端在10分钟内没有任何操作,那么即使客户端期望继续使用该会话,会话仍然会自动终止。会话如果是因为空闲超时的原因被终止,那么当客户端试图使用它的代理时,会获得一个CommunicationObjectFaultedException异常。在绑定中通过配置不同的值,可以为客户端和服务配置不同的超时值。支持可靠传输层会话的绑定提供了ReliableSession属性,类型为ReliableSession或者OptionalReliableSession。ReliableSession类定义了InactivityTimeout属性,属于TimeSpan类型,通过它可以配置一个新的空闲超时值。”

注意,InactivityTimeout属性的默认值为10分钟。不能将该值设置为小于或等于0的值,否则会抛出ArgumentOutOfRangeException异常。

例如,下面的代码利用编程方式将TCP绑定的空闲超时值配置为25分钟:

NetTcpBinding tcpSessionBinding = new NetTcpBinding(  );
tcpSessionBinding.ReliableSession.Enabled = true;
tcpSessionBinding.ReliableSession.InactivityTimeout = TimeSpan.FromMinutes(25);

这等同于配置config文件:

<netTcpBinding>
   <binding name = "TCPSession">
      <reliableSession enabled = "true" inactivityTimeout = "00:25:00"/>
   </binding>
</netTcpBinding>

如果客户端与服务都配置了超时值,则以短的超时值为准。

单例服务

如果我们熟悉设计模式,可以以单例模式的方式思考单例服务。所谓单例服务,就是针对所有客户端而言,都只有一个服务实例。“单例服务的生存期是无限的,只有在关闭宿主时,才会被释放。创建宿主时,单例服务会被创建,并且只能被创建一次。”

可以通过InstanceContextMode.Single的InstanceContextMode属性配置单例服务:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
class MySingleton : ...
{...}

只要是单例服务,即使该服务支持多个契约,这些契约中有的需要会话,有的不需要会话,在不同终结点的调用仍然是通过相同的实例进行传递。即使关闭了代理,也不会终止单例服务。

在实例化单例服务对象时,可能需要执行一些初始化的工作。如果使用默认的构造函数,并通过ServiceHost托管服务,是没有办法做到这一点的。当然,我们也可以在默认构造函数中实现这些初始化的工作,然而如果初始化工作需要一些特别的定制步骤,特别是需要操作状态或者需要传入参数时,默认的构造函数就显得捉襟见肘了。

WCF提供了另外一种初始化单例服务的办法,就是利用ServiceHost类提供的专门的构造函数,可以接收一个object对象:
public class ServiceHost : ServiceHostBase,...
{
   public ServiceHost(object singletonInstance,
                      params Uri[] baseAddresses);
   public virtual object SingletonInstance
   {get;}
   //More members
}

注意,构造函数中的singletonInstance必须是配置为单例方式的服务对象。因此,初始化以及托管单例服务的方式可以如下实现:
//Service code
[ServiceContract]
interface IMyContract
{
   [OperationContract]
   void MyMethod(  );
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
class MySingleton : IMyContract
{
   int m_Counter = 0;

   public int Counter
   {
      get
      {
         return m_Counter;
      }
      set
      {
         m_Counter = value;
      }
   }
   public void MyMethod(  )
   {
      m_Counter++;
      Trace.WriteLine("Counter = " + Counter);
   }
}
//Host code
MySingleton singleton = new MySingleton(  );
singleton.Counter = 42;

ServiceHost host = new ServiceHost(singleton);
host.Open(  );
//Do some blocking calls then
host.Close(  );

//Client code
MyContractClient proxy = new MyContractClient(  );
proxy.MyMethod(  );
proxy.Close(  );

//Output:
Counter = 43

很显然,ServiceHost提供的这个构造函数还有改进的余地,那就是object类型参数显然不具备类型安全,因而本书作者定义了新的ServiceHost类,引入了泛型:
public class ServiceHost<T> : ServiceHost
{
   public ServiceHost(T singleton,params Uri[] baseAddresses)
                                           : base(singleton,baseAddresses)
   {}
   public virtual T Singleton
   {
      get
      {
         if(SingletonInstance == null)
         {
            return default(T);
         }
         return (T)SingletonInstance;     
      }
   }
   //More members
}

单例服务与可伸缩性之间的关系可谓“水火不容”。因而除非是在特殊情况,应尽量避免使用单例服务。



原文地址:https://www.cnblogs.com/fx2008/p/2783863.html