I want to create a WCF service that uses the MSMQ binding because I have a large volume of notifications that the service must handle. It is important that clients are not delayed by the service and that notifications are processed in the order in which they were raised, therefore, the implementation of the queue.
Another consideration is sustainability. I know that I can compile MSMQ to make the queue more reliable, but I want to be able to run an instance of my service on different servers, so if server notification failures are not created in the queue and another server is processing.
I experimented with binding to MSMQ and found that you can have multiple instances of a service listening on the same queue, and leaving for themselves, they end up doing a kind of circuit with load balancing across available services. This is great, but I end up losing the sequence of queues as different instances take a different amount of time to process the request.
I use a simple console experiment application, which is epic code. When it starts, I get this output:
host1 open host2 open S1: 01 S1: 03 S1: 05 S2: 02 S1: 06 S1: 08 S1: 09 S2: 04 S1: 10 host1 closed S2: 07 host2 closed
I want this to happen:
host1 open host2 open S1: 01 <pause while S2 completes> S2: 02 S1: 03 <pause while S2 completes> S2: 04 S1: 05 S1: 06 etc.
I would think that as S2 does not finish, it can still fail and return the message that it was processing to the queue. Therefore, S1 should not be allowed to pull another message out of the queue. My turn is for transactions, and I tried setting TransactionScopeRequired = true in the service, but to no avail.
Is it possible? Am I really wrong? Is there any other way to create a failover service without any central synchronization mechanism?
class WcfMsmqProgram { private const string QueueName = "testq1"; static void Main() { // Create a transactional queue string qPath = ".\\private$\\" + QueueName; if (!MessageQueue.Exists(qPath)) MessageQueue.Create(qPath, true); else new MessageQueue(qPath).Purge(); // S1 processes as fast as it can IService s1 = new ServiceImpl("S1"); // S2 is slow IService s2 = new ServiceImpl("S2", 2000); // MSMQ binding NetMsmqBinding binding = new NetMsmqBinding(NetMsmqSecurityMode.None); // Host S1 ServiceHost host1 = new ServiceHost(s1, new Uri("net.msmq://localhost/private")); ConfigureService(host1, binding); host1.Open(); Console.WriteLine("host1 open"); // Host S2 ServiceHost host2 = new ServiceHost(s2, new Uri("net.msmq://localhost/private")); ConfigureService(host2, binding); host2.Open(); Console.WriteLine("host2 open"); // Create a client ChannelFactory<IService> factory = new ChannelFactory<IService>(binding, new EndpointAddress("net.msmq://localhost/private/" + QueueName)); IService client = factory.CreateChannel(); // Periodically call the service with a new number int counter = 1; using (Timer t = new Timer(o => client.EchoNumber(counter++), null, 0, 500)) { // Enter to stop Console.ReadLine(); } host1.Close(); Console.WriteLine("host1 closed"); host2.Close(); Console.WriteLine("host2 closed"); // Wait for exit Console.ReadLine(); } static void ConfigureService(ServiceHost host, NetMsmqBinding binding) { var endpoint = host.AddServiceEndpoint(typeof(IService), binding, QueueName); } [ServiceContract] interface IService { [OperationContract(IsOneWay = true)] void EchoNumber(int number); } [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] class ServiceImpl : IService { public ServiceImpl(string name, int sleep = 0) { this.name = name; this.sleep = sleep; } private string name; private int sleep; public void EchoNumber(int number) { Thread.Sleep(this.sleep); Console.WriteLine("{0}: {1:00}", this.name, number); } } }