Artech

Develop every application as an art using the most suitable technologies!

常用链接

统计

积分与排名

网上邻居

我的博文系列

最新评论

[原创]我的WCF之旅(12):使用MSMQ进行Reliable Messaging

一、为什么要使用MSMQ

在一个分布式的环境中,我们往往需要根据具体的情况采用不同的方式进行数据的传输。比如在一个Intranet内,我们一般通过TCP进行高效的数据通信;而在一个Internet的环境中,我们则通常使用Http进行跨平台的数据交换。而这些通信方式具有一个显著的特点,那就是他们是基于Connection的,也就是说,交互双方在进行通信的时候必须保证有一个可用的Connection存在于他们之间。而在某些时候,比如那些使用拨号连接的用户、以及使用便携式计算机的用户,我们不能保证在他们和需要访问的Server之间有一个的可靠的连接,在这种情况下,基于Messaging Queue的连接就显得尤为重要了。我们今天就来谈谈在WCF中如何使用MSMQ。

MSMQ不仅仅是作为支持客户端连接工具而存在,合理的使用MSMQ可以在很大程度上提升系统的Performance和Scalability。我们先来看看MSMQ能给我们带来怎样的好处:

1.MSMQ是基于Disconnection

MSMQ通过Message Queue进行通信,这种通信方式为离线工作成为了可能。比如在介绍MSMQ时都会提到的Order Delivery的例子:在一个基于B2C的系统中,订单从各种各样的客户传来,由于 客户的各异性,不能保证每个客户在每时每刻都和用于接收订单的Server保持一个可靠的连接,我们有时候甚至允许客户即使在离线的情况下也可以递交订单(虽然订单不能发送到订单的接收方,但是我们可以通过某种机制保证先在本地保存该订单,一旦连接建立,则马上向接收方递交订单),而MSMQ则有效地提供了这样的机制:Server端建立一个Message Queue来接收来个客户的订单,客户端通过向该Message Queue发送承载了订单数据的Message实现订单的递交。如果在客户离线的情况下,他仍然可以通过客户端程序进行订单递交的操作,存储着订单数据的Message会被暂时保存在本地的Message Queue中,一旦客户联机,MSMQ将Message从中取出,发送到真正的接收方,而这个动作对于用户的透明的。

2.MSMQ天生是One-way、异步的

在MSMQ中,Message始终以One-way的方式进行发送,所以MSMQ具有天生的异步特性。所以MSMQ使用于那些对于用户的请求,Server端无需立即响应的场景。也就是说Server对数据的处理无需和Client的数据的发送进行同步,它可以独自地按照自己的Schedule进行工作。这可以避免峰值负载。比如Server端可以在一个相对低负载的时段(比如深夜)来对接收到的Order进行批处理,而无需一天24小时一直进行Order的监听、接收和处理。

3.MSMQ能够提供高质量的Reliable Messaging

我们知道,在一般的情况下,如果Client端以异步的方式对Service进行调用就意味着:Client无法获知Message是否成功抵达Service端;也不会获得Service端执行的结果和出错信息。但是我们仍然说MSMQ为我们提供了可靠的传输(Reliable Messaging),这主要是因为MSMQ为我们提供一些列Reliable Messaging的机制:

  • 超时机制(Timeout):可以设置发送和接收的时间,超出该时间则被认为操作失败。
  • 确认机制(Acknowledgement):当Message成功抵达Destination Queue,或者被成功接收,向发送端发送一个Acknowledgement message用以确认操作的状态。
  • 日志机制(Journaling):当Message被发送或接收后,被Copy一份存放在Journal Queue中。

此外,MSMQ还提供了死信队列(Dead letter Queue)用以保存发送失败的message。这一切保证了保证了Reliable Messaging。

二、 MSMQ在WCF的运用

在WCF中,MSMQ提供的数据传输功能被封装在一个Binding中,提供WCF Endpoint之间、以及Endpoint和现有的基于MSMQ的Application进行通信的实现。为此WCF为我们提供了两种不同的built-in binding:

  • NetMsmqBinding:从提供的功能和使用 方式上看,NetMsmqBinding和一般使用的binding,比如basicHttpBinding,netTcpBinding没有什么区别:在两个Endpoint之间实现了数据的通信,所不同的是,它提供的是基于MSMQ的Reliable Messaging。从变成模式上看,和一般的binding完全一样。
  • MsmqIntegrationBinding:从命名上我们可以看出,MsmqIntegrationBinding主要用于需要将我们的WCF Application和现有的基于MSMQ的Application集成的情况。MsmqIntegrationBinding实现了WCF Endpoint和某个Message Queue进行数据的通信,具体来说,就是实现了单一的向某个Message Queue 发送Message,和从某个Message Queue中接收Message的功能。从编程模式上看,也有所不同,比如Operation只接收一个MsmqMessage<T>的参数。

这是Client和Service通信的图示:

三、MSMQ和Transaction

MSMQ提供对Transaction的支持。在一般的情况下,MSMQ通过Message Queue Transaction实现对Transaction的原生的支持,借助Message Queue Transaction,可以把基于一个或多个Message Queue的相关操作纳入同一个Transaction中。

Message Queue Transaction仅仅限于基于Message Queue的操作,倘若操作涉及到另外一些资源,比如SQL Server, 则可以使用基于DTC的分布式Transaction

对于WCF中MSMQ,由于Client和Service的相对独立(可能Client发送Message到Service处理Message会相隔很长一段时间),所以Client和Service的操作只能纳入不同的Transaction中,如下图。

四、Sample1:NetMsmqBinding

我们首先做一个基于NetMsmqBinding Sample,实现的功能就是我们开篇所提出的Order Delivery。我们说过,NetMsmqBinding和一般的binding在实现的功能和变成模式上完全一样。下面是我们熟悉的4层结构:


1.Contract

DataContract:Order & OrderItem

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;

namespace Artech.QueuedService.Contract
{
    [DataContract]
    [KnownType(
typeof(OrderItem))]
    
public class Order
    
{
        
Private Fields

        
Constructors

        
Public Properties

        
Public Methods
    }

}


using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;

namespace Artech.QueuedService.Contract
{
    [DataContract]
    
public class OrderItem
    
{
        
Private Fields

        
Constructors

        
Public Properties
    }

}

ServiceContract: IOrderProcessor

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;

namespace Artech.QueuedService.Contract
{
    [ServiceContract] 
    [ServiceKnownType(
typeof(Order))]
    
public interface IOrderProcessor
    
{
        [OperationContract(IsOneWay 
= true)]
        
void Submit(Order order);
    }

}

2.Service:IOrderProcessor

using System;
using System.Collections.Generic;
using System.Text;
using Artech.QueuedService.Contract;
using System.ServiceModel;

namespace Artech.QueuedService.Service
{
    
public class OrderProcessorService:IOrderProcessor
    
{
        
ISubmitOrder Members
    }

}

using System;
using System.Collections.Generic;
using System.Text;
using Artech.QueuedService.Contract;

namespace Artech.QueuedService.Service
{
    
public static class Orders
    
{
        
private static IDictionary<Guid, Order> _orderList = new Dictionary<Guid, Order>();

        
public static void Add(Order order)
        
{
            _orderList.Add(order.OrderNo, order);
        }


        
public static Order GetOrder(Guid orderNo)
        
{
            
return _orderList[orderNo];
        }

    }

}

3.Hosting

Configuration

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    
<system.serviceModel>
        
<bindings>
            
<netMsmqBinding>
                
<binding name="msmqBinding">
                    
<security>
                        
<transport msmqAuthenticationMode="None" msmqProtectionLevel="None" />
                        
<message clientCredentialType="None" />
                    
</security>
                
</binding>
            
</netMsmqBinding>
        
</bindings>
        
<services>
            
<service name="Artech.QueuedService.Service. OrderProcessorService">
                
<endpoint address="net.msmq://localhost/private/orders" binding="netMsmqBinding"
                    bindingConfiguration
="msmqBinding" contract="Artech.QueuedService.Contract.IOrderProcessor" />
            
</service>
        
</services>
    
</system.serviceModel>
</configuration>

在默认的情况下,netMsmqBinding 的msmqAuthenticationModeWindowsDomain,由于基于WindowsDomain必须安装AD,利于在本机模拟,我把msmqAuthenticationMode改为None,相应的ProtectionLevel和clientCredentialType改为None。

Program:

using System;
using System.Collections.Generic;
using System.Text;
using System.Messaging;
using System.ServiceModel;
using Artech.QueuedService.Service;

namespace Artech.QueuedService.Hosting
{
    
class Program
    
{
        
static void Main(string[] args)
        
{
            
string path = @".\private$\orders";
            
if(!MessageQueue.Exists(path))
            
{
                MessageQueue.Create(path,
true);
            }


            
using (ServiceHost host = new ServiceHost(typeof(OrderProcessorService)))
            
{
                host.Opened 
+= delegate
                
{
                    Console.WriteLine(
"Service has begun to listen\n\n");
                }
;

                host.Open();

                Console.Read();
            }

        }

    }

}

在Host Service之前,通过MessageQueue.Create创建一个Message Queue,第二个参数为表明Queue是否支持Transaction的indicator,这里支持Transaction。

4.Client:

Configuration

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    
<system.serviceModel>
        
<bindings>
            
<netMsmqBinding>
                
<binding name="msmqBinding">
                    
<security>
                        
<transport msmqAuthenticationMode="None" msmqProtectionLevel="None" />
                        
<message clientCredentialType="None" />
                    
</security>
                
</binding>
            
</netMsmqBinding>
        
</bindings>
        
<client>
            
<endpoint address="net.msmq://localhost/private/orders" binding="netMsmqBinding"
                bindingConfiguration
="msmqBinding" contract="Artech.QueuedService.Contract.IOrderProcessor"
                name
="defaultEndpoint" />
        
</client>
    
</system.serviceModel>
</configuration>

Program

using System;
using System.Collections.Generic;
using System.Text;
using Artech.QueuedService.Contract;
using System.ServiceModel;
using System.Transactions;

namespace Artech.QueuedService.Client
{
    
class Program
    
{
        
static void Main(string[] args)
        
{
            ChannelFactory
<IOrderProcessor> channelFactory = new ChannelFactory<IOrderProcessor>("defaultEndpoint");
            IOrderProcessor channel 
= channelFactory.CreateChannel();

            Order order 
= new Order(Guid.NewGuid(),DateTime.Today,Guid.NewGuid(),"A Company");
            order.OrderItems.Add(
new OrderItem(Guid.NewGuid(),"PC",5000,20));
            order.OrderItems.Add(
new OrderItem(Guid.NewGuid(),"Printer",7000,2));

            Console.WriteLine(
"Submit order to server");
            
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
            

                 channel.Submit(order);
                 scope.Complete();
            }
       
            Console.Read();
        }

    }

}

先后运行Host和Client,Host端有下面的输出:


WCF相关内容:
[原创]我的WCF之旅(1):创建一个简单的WCF程序
[原创]我的WCF之旅(2):Endpoint Overview
[原创]我的WCF之旅(3):在WCF中实现双向通信(Bi-directional Communication)
[原创]我的WCF之旅(4):WCF中的序列化(Serialization)- Part I
[原创]我的WCF之旅(4):WCF中的序列化(Serialization)- Part II
[原创]我的WCF之旅(5):Service Contract中的重载(Overloading)
[原创]我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案
[原创]我的WCF之旅(7):面向服务架构(SOA)和面向对象编程(OOP)的结合——如何实现Service Contract的继承
[原创]我的WCF之旅(8):WCF中的Session和Instancing Management
[原创]我的WCF之旅(9):如何在WCF中使用tcpTrace来进行Soap Trace
[原创]我的WCF之旅(10): 如何在WCF进行Exception Handling
[原创]我的WCF之旅(11):再谈WCF的双向通讯-基于Http的双向通讯 V.S. 基于TCP的双向通讯

[原创]我的WCF之旅(12):使用MSMQ进行Reliable Messaging
[原创]我的WCF之旅(13):创建基于MSMQ的Responsive Service

posted on 2007-06-29 00:57 Artech 阅读(5703) 评论(40)  编辑 收藏 网摘

评论

#1楼[楼主] 2007-06-29 02:10 Artech      

做自己的沙发,自我勉励一下。   回复  引用  查看    

#2楼 2007-06-29 07:38 Jeffrey Zhao      

需要注意的是,Dead-Letter Queues只能在Vista/Longhorn Server 下得到支持,此外还有Poison Queues也是如此,都是新特性。   回复  引用  查看    

#3楼[楼主] 2007-06-29 08:52 Artech      

@Jeffrey Zhao
Windows Vista, Windows Server 2003, and Windows XP differences

Message Queuing in Windows Vista supports subqueues, while subqueues are not supported in Windows Server 2003 and Windows XP. Subqueues are used in poison-message handling. The retry queues and the poison queue are subqueues to the application queue that is created based on poison-message handling settings. The MaxRetryCycles dictates how many retry subqueues must be created. On Windows Server 2003 and Windows XP, there is no support for subqueues. As such, MaxRetryCycles are ignored and ReceiveErrorHandling.Move is not allowed when executing on Windows Server 2003 or Windows XP platforms.

Message Queuing in Windows Vista supports negative acknowledgment while it is not supported in Windows Server 2003 and Windows XP. A negative acknowledgment from the receiving queue manager will cause the sending-side queue manager to place the rejected message in the dead-letter queue. As such, ReceiveErrorHandling.Reject is not allowed on Windows Server 2003 and Windows XP platforms.

Message Queuing in Windows Vista supports a message property to keep count of the number of times an attempt was made to deliver a message. This abort count property is not available on Windows Server 2003 and Windows XP. WCF maintains the abort count in memory, so it is possible that this property may not contain an accurate value when the same message is read by more than one WCF service in a farm.
  回复  引用  查看    

#4楼 2007-06-29 09:54 随风飘散      

向你学习   回复  引用  查看    

#5楼 2007-06-29 10:38 Jeffrey Zhao      

@Artech
是啊是啊,我就是这个意思。
  回复  引用  查看    

#6楼 2007-06-29 10:38 idior[未注册用户]

第二幅图应该是贴错了   回复  引用    

#7楼[楼主] 2007-06-29 11:50 Artech      

@idior
为什么?我只是想表明Client和Service不共享同一个Transaction。
  回复  引用  查看    

#8楼[楼主] 2007-06-29 11:51 Artech      

@随风飘散
@Jeffrey Zhao
:)
  回复  引用  查看    

#9楼 2007-06-29 13:23 idior[未注册用户]

MSMQ中主要涉及以下四种事务:
client, delivery, playback, service
其中service与playback可能是同一个事务也可能是两个不同的事务。当然service可能也不使用事务。

当然如果你只是想表明Client和Service不共享同一个Transaction,那也没有错。

  回复  引用    

#10楼 2007-06-29 14:50 江南白衣      

楼主的文章很不错!有打算写些wcf 的服务安全和操作安全方面的文章吗?   回复  引用  查看    

#11楼[楼主] 2007-06-29 16:51 Artech      

@江南白衣
其实我不会去刻意去写方面的东西,在学习、工作过程中接触到东西,或者某天突然想到了一个有意思的主题,如果觉得值得写就把它记录下来。

关于分布式环境下的Security是一个范围很宽泛、很多知识很多的话题,在今后我会针对某一个知识点发表我对Ditributed Security的看法。
  回复  引用  查看    

#12楼 2007-07-01 13:03 Sephil      

请问MSMQ如何发送消息给客户端
比如客户端发送消息给服务器端,服务器端运算后需要通知客户端,该怎么做
  回复  引用  查看    

#13楼[楼主] 2007-07-01 16:23 Artech      

@Sephil
实际上是没有直接的方式通过One-way的MEP获得调用的结果,只能通过MSMQ自有的一些机制,确认Message是否被Service接收、处理。

你可以参考http://www.cnblogs.com/artech/archive/2007/07/01/802069.html" target="_new">我的WCF之旅(13):创建基于MSMQ的Responsive Service
  回复  引用  查看    

#14楼 2007-07-02 08:47 Sephil      

OK,3x.刚看到你的新文章“创建基于MSMQ的Responsive Service ”,呵呵
还想问问,你的demo是基于WCF的,WCF对MQ的支持和1.1/2.0有什么区别么
  回复  引用  查看    

#15楼 2007-07-19 17:05 江南白衣      

请问楼主,MSMQ的队列长度有什么限制吗?   回复  引用  查看    

#16楼[楼主] 2007-07-20 22:12 Artech      

@江南白衣
对这个还真不太清楚,等我查查相关资料,有了结果在回答你:)
  回复  引用  查看    

#17楼 2008-04-15 13:58 小生动活泼[未注册用户]

有源码吗? 这样省得我们自已找。
  回复  引用    

#18楼 2008-04-15 14:03 小生动活泼[未注册用户]

楼主啊。

为什么不将源码打包呢
  回复  引用    

#19楼[楼主] 2008-04-16 14:50 Artech      

@小生动活泼
这篇没有Source Codehttp://www.cnblogs.com/Emoticons/qface/055243523.gif" alt="" />
  回复  引用  查看    

#20楼 2008-04-19 22:33 BAsil      

顶一个   回复  引用  查看    

#21楼 2008-08-05 13:14 wumingshi[未注册用户]

能获得MSMQ的长度? 我想在发送消息之前, 检查一下Destination Queue的长度,如果快满了,就等一下再发。   回复  引用    

#22楼 2008-12-11 16:21 wwwszx2002[未注册用户]

楼主:

感觉你这个例子,server和client使用的是同一个Queue store.如果在不同机器上怎么配置?还有就是如果用iis做host怎么实现,多谢楼主能指点.
  回复  引用    

#23楼 2008-12-22 15:01 糊糊      

楼主,你好.看完你的文章后,想请教你一个问题,就是:WCF是怎么将soap消息打包成msmq消息的?谢谢   回复  引用  查看    

#24楼[楼主] 2008-12-26 09:39 Artech      

@糊糊
通过NetMsmqBinding的Transport Binding Element!
  回复  引用  查看    

#25楼 2008-12-31 16:10 学习wcf中[未注册用户]

为什么我Host的时候提示Message Queuing has not been installed on this computer?   回复  引用    

#26楼 2008-12-31 16:14 学习wcf中[未注册用户]

解决了,呵呵   回复  引用    

#27楼 2008-12-31 16:33 学习wcf中[未注册用户]

请问下“.\private$\orders”代表什么意思
$又是什么意思
为什么这么定义
  回复  引用    

#28楼[楼主] 2009-01-05 08:20 Artech      

@学习wcf中
MSMQ路径就是这么表示的
  回复  引用  查看    

#29楼 2009-02-25 15:50 A&Qiang      

你好,请问客户端的配置文件可以通过VS208提供的"添加服务引用"功能配置吗?如果可以的话,服务地址怎么写,我输入net.msmq://localhost/private/orders地址,VS2008报找不到服务.   回复  引用  查看    

#30楼[楼主] 2009-03-02 16:11 Artech      

@A&amp;Qiang
添加服务引用的本质是倒入元数据。
元数据一般通过HTTP发布!
  回复  引用  查看    

#31楼 2009-06-16 14:00 williambirkin      

楼主这篇文章有些年头了,我现在才关注确实有点太不应该了。
不过我还是有点问题想请教下。因为初学wcf不久,请容忍我的无知:)

1.感觉Artech.QueuedService.Client是直接引用了Artech.QueuedService.Contract。Artech.QueuedService.Client.Program 直接使用了Artech.QueuedService.Client的数据类型Order和OrderItem对象。我觉得客户端应该是通过Server端发布的元数据来获得的Order和OrderItem的数据类型才对啊。

2.如果问题1中我的观点正确。那在客户端中获得的类型Order和OrderItem中好像只能包含属性。demo中的代码“order.OrderItems.Add(new OrderItem(Guid.NewGuid(), "PC", 5000, 20));”,如何实现呢?

3.对于初学了下wcf感觉SOA的架构设计和OO的还很不一样。感觉Demo中的Order这种复杂的数据类型(自己包含方法,包含数据集合,集合中的对象又有各自的方法)并不太适合作为wcf对外服务的数据类型。感觉wcf的服务和remoting不一样。remoting中传递的对象中是包含各种属性和方法的。所以客户端能如同本地程序集中的类型一样使用远程对象。但是感觉在wcf中,客户端需要调用的服务端方法方法永远是在服务端的serivce中的,也就是SerivceContract中定义的方法。也就是说方法永远是在Server端的,传到客户端的对象只是包含属性的数据对象,也就是DataContract中定义的。不知道我的这种理解对不对。
  回复  引用  查看    

#32楼[楼主] 2009-06-16 16:44 Artech      

@williambirkin
1. 如果你的Client和Service都自己定义,那么共享相同的Contract(Service Contract和Data Contract)是最好的选择。
2. 通过Medadata导入生成的DataContract,难道不能实现order.OrderItems.Add(new OrderItem(Guid.NewGuid(), "PC", 5000, 20));吗?这句代码有何特别之处?
3.Remoting和WCF本质上都是一样的,交换的都是数据,基本只包含上包含属性。难道Order类型不是这样定义的?
  回复  引用  查看    

#33楼 2009-06-16 17:18 williambirkin      

Artech.QueuedService.Service和Artech.QueuedService.Contract中的代码我没有修改。
我把server端的app.config修改了一下。主要是添加了Medadata的配置。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<netMsmqBinding>
<binding name="msmqBinding">
<security>
<transport msmqAuthenticationMode="None" msmqProtectionLevel="None" />
<message clientCredentialType="None" />
</security>
</binding>
</netMsmqBinding>
</bindings>
<services>
<service name="Artech.QueuedService.Service.OrderProcessorService" behaviorConfiguration="serviceBehavior">
<endpoint binding="netMsmqBinding" bindingConfiguration="msmqBinding" contract="Artech.QueuedService.Contract.IOrderProcessor" />
<endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" />
<host>
<baseAddresses>
<add baseAddress="net.msmq://localhost/private/orders"/>
<add baseAddress="http://localhost:8888"/>" target="_new">http://localhost:8888"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="serviceBehavior">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>

然后通过添加service引用,结果生成的Order类的OrderItems属性的代码如下
[System.Runtime.Serialization.DataMemberAttribute()]
public Artech.QueuedService.Client.ServiceReference1.OrderItem[] OrderItems {
get {
return this.OrderItemsField;
}
set {
if ((object.ReferenceEquals(this.OrderItemsField, value) != true)) {
this.OrderItemsField = value;
this.RaisePropertyChanged("OrderItems");
}
}
}

Order.OrderItems属性不是服务器端的IList<OrderItem>类型,而是OrderItem[] 。所以我的“Client中order.OrderItems.Add(new OrderItem(Guid.NewGuid(), "PC", 5000, 20))”这段代码是没法正常执行的。
不知道这是否正常。
  回复  引用  查看    

#34楼[楼主] 2009-06-16 18:01 Artech      

@williambirkin
IList<OrderItem>, OrderItem[]从元数据的描述来讲是等效的。在“添加服务引用”的时候,你可以设定生成的类型是List<T>还是Array!
  回复  引用  查看    

#35楼 2009-06-16 18:18 williambirkin      

唉,刚才傻了,忘记这个是可以手动改的。
多谢提醒。
继续拜读后面的文章。
  回复  引用  查看    




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 799529




相关文章:

相关链接: