博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

可信赖会话

  WCF的可信赖会话在绑定层保证消息只会被传输一次,并且保证消息间的顺序。当使用TCP通信时,协议本身保证了可靠性,但它只在两点间的网络 包这个层面提供了这样的保证。WCF的可信赖会话特性保证了在传输过程中消息不会丢失、重复或错位。这种保证是消息层面的,且适用于任何数目节点间的通 信。另外,使用可信赖会话时,WCF会重连掉线的连接,在重连失败时还会释放会话占用的相关资源。可信赖会话还会通过调整消息的发送频率来缓解网络拥挤。

  为使用WCF的可信赖会话,必须选择支持可信赖会话的绑定。支持这一特性的预定义绑定包括WSHttpBinding、 WSDualHttpBinding、WSFederationBinding、NetTcp-Binding和 NetNamedPipesBinding。在WSHttpBinding、WSDualHttpBinding和 WSFederationBinding的情况下,可信赖特性默认是关闭的,对其他绑定而言,默认是打开的。打开或关闭可信赖会话特性只要按如下方式自定 义绑定即可:

复制代码
<system.serviceModel>
<services>
<service>
<endpoint binding="wsHttpBinding" bindingConfiguration="MyReliableConfiguration"
[...]
/>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="MyReliableConfiguration">
<reliableSession enabled="true" ordered="true"/>
</binding>
</wsHttpBinding>
</bindings>
</system.serviceModel>
复制代码

  也可以通过包含System.ServiceModel.Channels.ReliableSessionBindingElement绑定元素为自定制的绑定添加可信赖会话特性。

复制代码
<system.serviceModel>
<services>
<service name="[...]">
<endpoint
[...]
binding
="customBinding"
bindingConfiguration
="MyReliableCustomBinding">
</endpoint>
</service>
</services>
<bindings>
<customBinding>
<binding name="MyReliableCustomBinding">
<reliableSession ordered="false"/>
<httpTransport />
</binding>
</customBinding>
</bindings>
</system.serviceModel>
复制代码

  WCF开发者可以指出他们的代码将依赖于对消息传送的保证。比如,他们可以指出将假定消息按照它们发出的顺序传送。

[ServiceContract(SessionMode=SessionMode.Required)]
[DeliveryRequirements(RequireOrderedDelivery
=true)]
publicinterface IMyServiceContract

  为服务契约添加这样的定义将导致WCF检查任何包含该服务契约的终结点,以确认它们选择了支持可信赖会话的绑定,并且被正确地配置以保证按顺序传送。

会话管理

  会话管理允许WCF应用程序将其接收到的消息作为会话的一部分来对待,也就是作为与另一个应用程序进行交换的统一序列消息的一部分。

  所以,WCF的应用程序开发者就可以通过如下的方式来编码处理消息:一个消息的处理需要依靠前一个消息的信息。如果需要编写这样的代码,可以将 System.Service-ModelServiceContract特性的SessionMode参数设为 System.ServiceModel.SessionMode.Required值:

[ServiceContract(SessionMode=SessionMode.Required)]
publicinterface IMyServiceContract

  这将让WCF检查为契约中包含的终结点选择的绑定是否支持会话,并将信息合并到消息中以识别会话。支持该功能的预定义绑定包括 WSHttpBinding、WSDualHttp-Binding、WSFederationBinding、NetTcpBinding、 NetNamedPipesBinding和NetMsmqBinding。

  开发者可以在实例上下文会话中存储或检索与会话相关的数据:

复制代码
publicclass MyExtension : IExtension<InstanceContext>
{
publicstring sessionIdentifier =null;
public MyDataType MyData =null;
}

publicvoid FirstRequest(MyDataType myData)
{
MyExtension extension
=new MyExtension();
extension.sessionIdentifier
= OperationContext.SessionId;
extension.MyDataType
= myData;
OperationContext.InstanceContext.Extensions.Add(myData);
}

public MyDataType SubsequentRequest()
{
Collection
<MyExtension> extensions = OperationContext.InstanceContext.
Extensions.FindAll
<MyExtension>();
foreach(MyExtension extension in extensions)
{
if(string.Compare(extension.sessionIdentifier, OperationContext.SessionId, true)==0)
return extension.MyData;
}
returnnull;
}
复制代码

  为更好地管理会话的相关资源,开发者可指定哪些操作开始会话,哪些操作结束会话:

复制代码
[ServiceContract(SessionMode=SessionMode.Required)]
publicinterface IMyServiceContract
{
[OperationContract(IsInitiating
=true)]
void StartSession();
[OperationContract(IsTerminating
=true)]
void StopSession();
}
复制代码

队列交付

  可信赖会话提供的消息传送保证只能作用在宿主程序域的生命周期里。如果一个消息在传送过程中丢失了,但发送消息的程序域在发现消息丢失前终止了,当程序域重新运行后,它就不知道这个消息已经丢失了。实际上,它将丢掉整个被丢失的消息所在会话的上下文。

  WCF能够通过MSMQ(Microsoft Message Queuing,Microsoft消息队列)发送消息,这为消息传送提供了独立于发送和接收应用程序域的生命周期的保证。队列代表接收应用程序存储发送 应用程序发出的消息。在消息已经存储到队列之后的某个时间,接收应用程序将获得这个消息。

  优先:

  1. 如果接收应用程序因为某些原因(如网络中断等)不能接收消息,发送应用程序仍可以发送消息然后继续做其他事件。消息将一直放在队列里,直到接收应用程序可以接收这些消息为止。

  2. 发送和接收应用程序间的网络带宽并不是一致的,使用MSMQ的主要限制是MSMQ只能存储小于4MB的消息。

  3. 接收应用程序不应该被不可预测的大量请求压垮。应用程序可以从队列里以它希望的速度获取消息。

  4. MSMQ是一项大多数系统管理员都熟悉的技术,操作系统也提供了Microsoft Management Console管理单元来管理MSMQ。

  队列可以用在两个WCF应用程序间的通信,也可以用于WCF应用程序与一个使用MSMQ的非WCF应用的通信。

  为使用MSMQ队列从一个WCF应用程序发送消息到另一个WCF应用程序,可以使用预定义的NetMsmqBinding。

复制代码
<services>
<service name="Fabrikam.TradeRecorder">
<host>
<baseAddresses>
<add baseAddress="net.msmq://localhost/private/"/>
</baseAddresses>
</host>
<endpoint address="EndpointAddress" binding="netMsmqBinding" [...] />
</service>
</services>
复制代码

  使用预定义NetMsmqBinding绑定的终结点,必须在地址里使用net.msmq策略。地址的下一段指定了队列所在的主机。如果队列是私有的,那么private段是必需的。最后一段是终结点自身的地址,它必须是指定主机上的事务性MSMQ队列的名称。

  被配置成通过MSMQ队列接收消息的服务必须和队列本身部署在同一台机器上。这个限制是由于MSMQ只允许对非事务队列进行远程读取,而WCF只能使用NetMsmq-Binding绑定从事务队列接收消息。

  可以配置一个服务的多个实例从同一个队列接收消息。在这种情况下,最空闲的应用程序将接收到下一个消息。

  当一个终结点被配置为从队列获取消息的预定义绑定,WCF将确认终结点契约的所有操作都显式地声明为单向(one-way)。

复制代码
publicinterface IMyServiceContract
{
[OperationContract(IsOneWay
=true)]
void FirstOperation(string input);
[OperationContract(IsOneWay
=true)]
void SecondOperation(string input);
}
复制代码

  WCF应用程序的开发者可以为服务契约添加一个特性以指定应用程序将通过队列接收消息。

[ServiceContract(SessionMode=SessionMode.Required)]
[DeliveryRequirements(QueueDeliveryRequirements
=QueueDeliveryRequirementsMode.Required]
publicinterface IMyServiceContract

  如果开发者这样做,WCF将确认包含这些服务契约的任何终结点的绑定都是通过队列将消息传送给服务的绑定。

Windows Vista的改进

  Windows Vista和其之后的操作系统对MSMQ进行了一些改进,这些改进与dead-letter和有毒队列(poison queue)有关。

dead-letter队列

  MSMQ消息可以配置要到达的目的队列和从目的队列接收的时间。当这些时间中任何一个过期时,消息将会放到一个被称作dead-letter队 列的系统消息队列中。另外,如果消息被发往事务队列,而发送端的队列管理器没有接收到消息已经从目的队列中读取的肯定确认时,这个消息将被转移到一个事务 dead-letter队列。在Windows Vista和其后的操作系统里,可以创建一个队列并将其指定为另一个队列的dead-letter队列。WCF提供了这项改进的支持,开发者可以为使用队 列发送和接收消息的应用程序指定另一个队列作为dead-letter队列。

复制代码
<client>
<endpoint address="net.msmq://localhost/private$/EndpointAddress"
binding
="netMsmqBinding" bindingConfiguration="QueueBinding"
[...]
/>
</client>
<bindings>
<netMsmqBinding>
<binding name="QueueBinding" deadLetterQueue="Custom"
customDeadLetterQueue
="net.msmq://localhost/private$/myDeadLetterQueue">
</binding>
</netMsmqBinding>
</bindings>
复制代码

有毒队列

  有毒消息是事务队列中不能被接收应用程序处理的消息。当消息从队列中读取而接收应用程序不能处理它时,应用程序会回滚读取这个消息的事务,这个消息也将会被放回到队列中。应用程序再一次读取这个消息,读取和回滚该有毒消息的过程就会无限循环下去。

  在Windows Vista之前,MSMQ本身并没有提供检测或复制有毒消息的功能。WCF为此提供了帮助机制,开发者可以为ReceiveRetryCount和ReceiveErrorHanding属性指定合适的值。

复制代码
<services>
<service name="Fabrikam.TradeRecorder">
<host>
<baseAddress>
<add baseAddress="net.msmq://localhost/private/"/>
</baseAddress>
</host>
<endpoint address="EndpointAddress" binding="netMsmqBinding"
bindingConfiguration
="QueueBinding"
[...]
/>
</service>
</services>
<bindings>
<netMsmqBinding>
<binding name="QueueBinding" receiveRetryCount="0" receiveErrorHandling="Fault">
</binding>
</netMsmqBinding>
</bindings>
复制代码

  ReceiveRetryCount属性的值定义了什么样的消息是有毒消息--就是那些回滚到队列的次数超过 ReceiveRetryCount属性值的消息。ReceiveErrorHanding属性的值指定了对有毒消息的操作。它的值的选择包含将服务改成 出错状态从而不能继续接收任何消息,或者忽略这个有毒消息消息等。

  Windows Vista和之后的操作系统为此提供了更多的选项。把ReceiveErrorHandling的值设为Move会将有毒消息返回到其发送源。这种情况 下,消息最后会被放入发送源的dead-letter队列里。还有一个选项,把ReceiveErrorHandling的值设为Reject,有毒消息 将会被移到接收队列的一个有毒子队列中。对于一个地址为net.msmq://local-host/privateEndpointAddress的队 列而言,其有毒子队列的地址为net.msmq://localhost/private/EndpointAddress:Poison。

事务

  WCF在预定义绑定中实现了标准的WSAtomicTransaction(WS-AT)协议和Microsoft专有的OleTx协议,这些协议可以用来在消息中加入事务状态的信息。WCF的开发者可以指定将一个操作的代码放在一个事务范围里执行。

复制代码
[ServiceContract]
publicinterface IMyServiceContract
{
[OperationContract(IsOneWay
=false)]
[TransactionFlow(TransactionFlowOption.Required)]
void MyMethod();
}

publicclass MyServiceType : IMyServiceContract
{
[OperationBehavior(TransactionScopeRequired
=true, TransactionAutoComplete=true)]
vod MyMethod()
{
[...]
}
}
复制代码

  任何被指定的必须在一个事务范围里执行的操作都不能被标记为单向方法,因为在操作结束时,有关事务状态的信息必须传回给调用者。TransactionAutoComplete特性指出没有异常发生时,WCF应该自动为操作提交事务。

  如果开发者决定由操作自己来提交事务,可使用WCF的静态System.ServiceModel.OperationContext对象:

复制代码
publicclass MyServiceType
{
[OperationBehavior(TransactionScopeRequired
=true, TaansactionAutoComplete=false)]
void MyMethod()
{
//Work gets done here
OperationContext.Current.SetTransactionComplete();
}
}
复制代码

  如果开发者指定服务的一个操作必须在一个事务的上下文中执行,那么WCF将确认服务配置了一个支持在消息里发送有关事务状态的绑定。提供了该支 持的预定义绑定包括:WSHttpBinding、WSDualHttpBinding、WSFederationBinding、 NetTcpBinding和NetNamedPipesBinding。最后两个绑定允许选择WS-AT协议或OleTx协议,而其他绑定都只使用标准 的WS-AT协议。

复制代码
<bindings>
<netTcpBinding>
<binding name="..." transactionFlow="true" transactionProtocol="OleTransactions"/>
</netTcpBinding>
<wsHttpBinding>
<binding name="..." transactionFlow="true"/>
</wsHttpBinding>
</bindings>
复制代码

  WCF的客户端开发者可以使用System.Transaction命名空间提供的语法将服务的操作放进一个事务的范围内。

复制代码
ServiceClient client =new ServiceClient("MyServiceEndpointConfiguration");
using(TransactionScope scope =new TransactionScope(TransactionScopeOption.RequiresNew))
{
client.DoSomething([...]);
scope.Complete();
}
client.Close();
复制代码

  如果服务的操作支持事务,且配置了支持传送有关事务状态的绑定,那么客户端的事务直到服务操作决定提交时才会提交。相反,如果客户端决定不提交事务,服务在客户端事务范围里对事务资源所做的任何操作都将被回滚。

  但这样的设计并不太好,当远程客户端决定将采取何种动作的同时,服务的操作和其资源都会被一直占用。作为一个通用的原则,应该尽量避免使事务超出可信赖范围。

  下图显示了一种更加巧妙地使用WCF实现分布式事务的方法。该方法融合了WCF对会话管理、队列交付和事务消息的支持。

  图中的服务被配置成通过队列从客户端接收消息。客户端首先依次开始一个事务和会话,然后发送一组消息到服务。客户端完成会话,当且仅当客户端提 交该事务时,它发送的消息才会到达队列中。客户端发送的这些单独消息在队列里将会表示成单一条目。在服务端,WCF在队列中检测到这条消息后,会依次开始 一个事务和会话。然后它将单一消息重新分拆成客户端发送的一组单个消息,并将它们一一发送给服务。如果一切顺利,服务会关闭会话然后提交事务。如果在处理 任何消息的过程中出现任何差错,事务将会取消,也就是说,这种情况下,对事务资源中的任何消息,所做的任何处理都会被回滚。这组消息将被转移到有毒消息队 列中,对失败操作需要采取补救措施的服务可以从中读取消息。

  这个设计的优点是,在客户端和服务端的操作不会被对方或它们之间的连接延迟所挂起。客户端和服务端的事务都是独立提交的。客户端的事务保证客户 端保持一致性状态,而服务端的事务保证服务一直处于一致性状态。如果客户端在它的事务范围内失败,服务端不会受影响,因为客户端所发送的消息作为事务的一 部分,不会被传送到服务端的队列中。如果服务端在处理客户发送的消息时失败,尽管客户端和服务端都处于一致性状态,但客户端和服务端的状态并没有相互一 致。在这种情况下,就需要采取一些措施来弥补这种不一致性状态