It is not a surprise that I have a keen interest in queuing system and messaging. I have been pondering building another queuing system recently, and that led to some thinking about the nature of queuing systems.
In general, there are two major types of queuing systems. Remote Queuing Systems and Local Queuing Systems. While the way they operate is very similar, they are actually major differences in the way you would work with either system.
With a Local Queuing System, the queue is local to your machine, and you can make several assumptions about it:
- The queue is always going to be available – in other words: queue.Send() cannot fail.
- Only the current machine can pull data from the queue – in other words: you can use transactions to manage queue interactions.
- Only one consumer can process a message.
- There is a lot of state held locally.
Examples of Local Queuing Systems include:
- Rhino Queues
A Remote Queuing System uses a queue that isn’t stored on the same machine. That means that:
- Both send and receive may fail.
- Multiple machines may work against the same queue.
- You can’t rely on transactions to manage queue interactions.
- Under some conditions, multiple consumers can process the same message.
- Very little state on the client side.
An example of Remote Queuing System is a Amazon SQS.
Let us take an example of simple message processing with each system. Using local queues, we can:
using(var tx = new TransactionScope())
var msg = queue.Receive();
// process msg
There are a lot actually going on here. The act of receiving a message in transaction means that no other consumer may receive it. If the transaction complete, the message will be removed from the queue. If the transaction rollbacks, the message will become eligible for consumers once again.
The problem is that this pattern of behavior doesn’t work when using remote queues. DTC are a really good way to kill both scalability and performance when talking to remote systems. Instead, Remote Queuing System apply the concept of a timeout.
var msg = queue.Receive( ackTimeout: TimeSpan.FromMniutes(1) );
// process msg
When the message is pulled from the queue, we specify the time that we promise to process the message by. The server is going to set aside this message for that duration, so no other consumer can receive it. If the ack for successfully processing the message arrives in the specified timeout, the message is deletes and everything just works. If the timeout expires, however, the message is now available for other consumers to process. The implication is that if for some reason processing a message exceed the specified timeout, it may be processed by several consumers. In fact, most Remote Queuing Systems implement a poison message handling so if X number of time consumers did not ack a message in the given time frame, the message is marked as poison and moved aside.
It is important to understand the differences between those two systems, because they impact the system design for systems using it. Rhino Service Bus, MassTransit and NServiceBus, for example, all assume that the queuing system that you use is a local one.
A good use case for a remote queuing system is when your clients are very simple (usually web clients) or you want to avoid deploying a queuing system.