Building a Pub/Sub Message Bus with WCF and MSMQ
In my previous blog I demonstrate how to build a offline distributed system with MSMQ. In this blog I will show you how to build a Publish/Subscribe message bus using WCF and MSMQ. The solution is built with .NET Framework 3.0 and makes extensive use of Windows Communication Foundation (WCF), Microsoft Message Queuing (MSMQ) 4.0 and Internet Information Services (IIS) 7.0, all hosted on Windows Server 2008. Now follow me.
1. Defining the Service Contract
The first step was to define the contracts which the publisher would use to notify any subscribers that an interesting event occurred. In our case we had a number of different types of events, but in order to reuse as much code as possible we used a generic service contract:
ServiceContract]
public interface IEventNotification
{
[OperationContract(IsOneWay = true)]
void OnEventOccurred(TLog value);
}
Now for any given event type, we can simply define a data contract to carry the payload (not shown here), and provide a derived service contract type as shown below:
[ServiceContract]
public interface IAccountEventNotification : IEventNotification
2. Implementing the Publisher
One of the key aspects of a publisher/subscriber pattern is that there should be ultra-loose coupling between the publisher and the subscriber. Critically, the publisher should not know anything about the subscribers, including how many there are or where they live. Originally we tried using MSMQ's PGM multicasting feature to accomplish this - essentially this lets you define a single queue address that will stealthily route the same message to multiple destination queues. While this feature does work, it had a couple of limitations that made it inappropriate in our scenario. First, the only way to use multicast queue addressing with WCF is to use the MsmqIntegrationBinding, which is less flexible than the NetMsmqBinding. Second, multicast addressing only works with non-transactional queues, which would have had an unacceptable impact of the reliability of our system.
So we abandoned this option and decided to implement our own lightweight multicasting directly within the publisher code. While technically this breaches the golden rule of the publisher knowing nothing about the subscribers, the information about the subscribers is completely contained in a configuration file. This means we can add, change or remove subscribers before or after deployment with no impact on the application code.
We had already built a component we called the ServiceFactory (no relation to the p&p Web Service Software Factory) which is a simple abstraction for creating local or WCF instances via a configuration lookup. This component isn't publicly available, but you could easily substitute your favourite Dependency Injection framework and achieve similar results. In our case, the web.config for one of our web services may have its dependent services defined as follows:
Previously we had used the ServiceFactory for creating individual instances, with code like this:
IEmailUtility email = ServiceFactory.GetService
As you can see from the configuration above, this would result in a singleton instance of a local class called EmailUtility being returned, but different configuration could result in a WCF proxy being returned instead. It was a simple matter to reuse this same ServiceFactory component to return all configured services matching a specific contract. We used this capability to build the NotificationPublisher class as follows:public class
NotificationPublisher
{
public static void OnEventOccurred(TLog value)
{
List
foreach (TInterface subscriber in subscribers)
{
subscriber.OnEventOccurred(value);
}
}
}
With this class in place, all that is required for the publisher to publish event is to instantiate a NotificationPublisher with the appropriate generic parameters and call the OnEventOccurred method. Assuming we are using the IAccountEventNotification interface and the above configuration, this would result in the event being fired over WCF to the services defined by the SubscriberXAccountEventNotification and SubscriberYAccountEventNotification endpoints.
3. Configuring the Publisher
The final missing piece on the publisher side is the WCF configuration. As mentioned previously, we chose to use MSMQ to provide reliable, asynchronous message delivery. Programming with MSMQ used to be quite a painful experience, but with WCF the programming model is no different than for any other transport - all you need to do is configure the right bindings. In our case we chose the NetMsmqBinding, which provides full access to WCF functionality for core MSMQ features (as opposed to the MsmqIntegrationBinding, which provides richer MSMQ support at the cost of more limited WCF functionality).
Here's an example of the client-side WCF configuration.
<system.serviceModel>
<bindings>
<netMsmqBinding>
<binding name="TransactionalMsmqBinding" exactlyOnce="true" deadLetterQueue="System" />
netMsmqBinding>
bindings>
<client>
<endpoint name="SubscriberXAccountEventNotification" address="net.msmq://localhost/private/SubscriberX/accounteventnotification.svc" binding="netMsmqBinding" bindingConfiguration="TransactionalMsmqBinding" contract="MyProject.Contracts.IAccountEventNotification" />
<endpoint name="SubscriberYAccountEventNotification" address="net.msmq://localhost/private/SubscriberY/accounteventnotification.svc" binding="netMsmqBinding" bindingConfiguration="TransactionalMsmqBinding" contract="MyProject.Contracts.IAccountEventNotification" />
client>
system.serviceModel>
There's nothing too fancy in this - the key thing to note is the exactlyOnce="true" setting which is required for transactional queues. The other thing that my stand out is the unusual net.msmq:// addressing syntax, which is used by the NetMsmqBinding in lieu of the more familiar FormatName addresses. The queues themselves are private queues called "SubscriberX/accounteventnotification.svc" and "SubscriberY/accounteventnotification.svc". Why did I give the queues such silly names? Read on...
4. Hosting and Configuring the Subscribers
In the past, if building MSMQ clients was annoying, building MSMQ services was a nightmare. You had to build your own host (typically in an NT Service) or make use of the somewhat inflexible MSMQ Triggers functionality. You then had to do a whole lot of work to ensure your service didn't lose messages, and that it wasn't killed by "poison messages", which are messages that will constantly cause your service to fail due to a malformed payload or problems with the service.
Just like on the client side, WCF takes a lot of the hard work away on the service side - but it doesn't directly help with hosting the service and listening to the queue. Luckily this problem is solved beautifully by IIS 7.0 and Windows Activation Services (WAS), which is available on Windows Vista and Windows Server 2008. In a nutshell this enables IIS to listen to MSMQ, TCP and Named Pipes and activate your WCF service, just as IIS 6.0 does for HTTP. If this all sounds great, it is - but be warned that it can be a bit fiddly to set up.
First, you need to set up an "application" in IIS that points to your service, including the .svc file and the web.config file. This is no different to what you'd normally do for an IIS-hosted service exposed over HTTP.
Next, you need to create the message queue - you can do this with the Computer Management console in Vista or Server Manager in Windows Server 2008. The name of the queue must match the application name plus the .svc file name, for example "SubscriberX/accounteventnotification.svc" (this fact is unfortunately not well documented). Make sure you mark the queue as transactional when you create it, as you can't change this later. You'll also need to set permissions on the queue so that the account running the "Net.Msmq Listener" service (NETWORK SERVICE by default) can receive messages, and whatever account is running the client/publisher can send messages (NETWORK SERVICE by default, too).
Finally you'll need to configure IIS and WAS to enable the Net.Msmq listener for the web site, and for the specific application (make sure you've installed the Windows components for WAS and non-HTTP activation before you proceed!). The easiest way to do this is using appcmd.exe which lives in the System32InetSrv folder:
As you might expect, this looks pretty similar to the client configuration you saw earlier.
One thing worth calling out here is the receiveErrorHandling="Move". This innocent-looking attribute probably saved us a month of work, as it tells WCF to move any messages that have repeatedly failed to be processed onto an MSMQ subqueue called "poison" and continue processing the next message, rather than the faulting the service. Note that subqueues, as well as the long-awaited ability to transactionally read from a remote queue, are some more new features in MSMQ 4.0 in Vista and Windows Server 2008.
5. Implementing the Subscribers
The only thing remaining is to implement the subscriber. Most of the code will of course be specific to the business requirements, so I'll only spend time describing the implementation of the service interface. In our system it is very important to make sure that no messages are accidentally lost. Since MSMQ can provide guaranteed delivery it may not be obvious how a message could just vanish. In fact most messages are lost after MSMQ has successfully delivered the message to the service. This can happen if the service receives the message and then fails before the message is successfully processed (possibly due to a bug or configuration problem). The best way of avoiding this problem is to use a transaction that spans receiving the message from the queue and any processing business logic. If anything fails, the transaction will be rolled back - including receiving the message from the queue! If the problem was a temporary glitch, the message may be successfully processed again. If the problem is permanent or caused by a malformed message, the message will be considered to be "poison" after several retries, and as mentioned earlier will be moved to a special "posion" subqueue where it can be dealt with manually by an administrator.
Making all of this work is surprisingly simple, since all of these capabilities are supported by MSMQ (provided you're using transactional queues) and WCF. All that you need to do is decorate your service implementation methods with a couple of attributes that state that your business logic should enlist in the transaction started when the message was pulled off the queue.
public class NotificationService : IAccountEventNotification
{
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public void OnEventOccurred(AccountEventLog value)
{
// Business-specific logic
}
}
6. Conclusion
While this has been one of the longer blog posts I've done in a while, the solution is extremely powerful and surprisingly simple to implement thanks to some great advances in WCF, MSMQ and IIS. In the past, many people (including myself) have spent months trying to implement pub/sub patterns, often with less-than-spectacular results. But using these new technologies eliminates huge amounts of custom code - in fact the few code and configuration snippets in this post are really all that it takes to make this work.
For more readings on this subject, refer to :
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/13651903/viewspace-1034244/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 【Lintcode】1786. Pub Sub Pattern
- SpringBoot Redis 釋出訂閱模式 Pub/SubSpring BootRedis模式
- Redis的Pub/Sub客戶端實現Redis客戶端
- 使用 EMQX Cloud 橋接資料到 GCP Pub/SubMQCloud橋接GC
- 不使用 MQ 如何實現 pub/sub 場景?MQ
- redis原始碼分析之釋出訂閱(pub/sub)Redis原始碼
- 基於Pub/Sub模式的阿里雲IoT同步呼叫詳解模式阿里
- Spotify如何從Apache kafka遷移到雲平臺的pub/sub系統ApacheKafka
- Redis 中使用 list,streams,pub/sub 幾種方式實現訊息佇列Redis佇列
- Akka-Cluster(2)- distributed pub/sub mechanism 分散式釋出/訂閱機制分散式
- Arduino BuildingUI
- How to add sub-repo as sub-module
- 解釋一下這兩行 "pub": "pnpm --filter "./packages/*" run pub", "pub:beta": "pnpm --filter "./packages/*" run pub:beta"NPMFilterPackage
- 如何在C#中使用MSMQC#MQ
- pub.jsJS
- MSMQ 觸發器 安裝失敗MQ觸發器
- uniapp BusAPP
- WCF系列教程地址
- HTML <sub> 下標HTML
- C# WCF入門C#
- WCF雙工通訊
- Building an Automatically Scaling Web ApplicationUIWebAPP
- 1469C Building a FenceUI
- Java與WCF互動(一):Java客戶端呼叫WCF服務 (轉)Java客戶端
- WCF服務端的.NET Core支援專案Core WCF 正式啟動服務端
- [WCF許可權控制]利用WCF自定義授權模式提供當前Principal模式
- IIS部署WCF詳細教程
- 艾偉:WCF安全之EndPointIdentityIDE
- CF1316E Team BuildingUI
- Building OpenNI using a cross-compilerUIROSCompile
- Android Handler機制之Message及Message回收機制Android
- 【Lintcode】1856. Sub-palindrome
- Pegging a sub strapon in Shanghai/Suzhou/WuxiAIUX
- SpringCloud 2020.0.4 系列之 BusSpringGCCloud
- 學習WCF之路,長期更新
- Azure Service Bus(二)在NET Core 控制檯中如何操作 Service Bus Queue
- Reliable Key Holder for Chastity Sub Anywhere in the WorldAST
- 蜆子(Shell sub,Son clam,Clams)
- 下一gen AI Bus指南AI