Friday, September 24, 2010

Thoughts on Message Queues and HTTP

I have been facing this dilemma for a couple of years where I find a need for asynchronous loosely coupled communication.

Requirements


  1. Decoupling of publishers and subscribers.
  2. Reliable message delivery.
  3. Ability to connect, disconnect, reconnect clients without losing messages.
  4. Ability to shut down and restart the messaging server without losing messages.
  5. Ability to 'query' or 'filter' messages based on their category.
  6. No low latency requirement - messages delivered within 'seconds' is enough.
  7. No high throughput requirement - no need to scale to many thousands of messages per second.


Pub-Sub And Message Queues


Naturally, I would turn to use message queuing as a solution. What I found was that while most queue products claim to be very performant having a high throughput, none of the ones I have tried managed to deliver on reliability consistently. These products are great if you only need a best-effort, non-guaranteed, asynchronous communication channel. But sadly, when the reliability requirement is added (that is durable queues and topics) the complexity of the solutions becomes a problem. Not only that, but there are often other limitations that come into play - like performance problems even for low throughput requirements in the low hundreds of messages per second.

If you look at my requirement list, it is doable with message queues - absolutely. The problem is that it is very hard to do well the way message queues do it. The root of the problem is that durable subscriptions are tracked within the server. When the message delivery is tracked at the server things suddenly become very complex. What do you do with subscriptions that are never removed (happens all the time in dynamic environments), how do you do clustering, load balancing etc. While these things are all doable, it is very hard to do it well.

I have watched ActiveMQ for many many years now, and I have been using it almost as long. It's a good product but it has taken amazingly long for it to become somewhat solid. Even then, it only works in the very basic cases reliably; that is unreliable, non-durable, messaging. The second problem tends to be the spartan all around support for clients on various platforms - now people would come out of the woodwork to argue that there are clients for all kinds of platforms, but the problem is that they are not what the Java client is. For many clients, you still have to deal with very basic issues like client reconnects on your own (a little detail that people new to these tools always get wrong). This is not unique to ActiveMQ - HornetMQ, RabbitMQ etc. all suffer from very basic level problems when you try to do anything with reliable message delivery.

I have not mentioned one feature that I absolutely like to have; that is the ability to browse the event streams, anywhere in time, at any time, by any client, forward, or backward. Message Queues can not do this. It is possible to browse unconsumed messages in a queue, but when the most used use-case in my situation needs a topic, this goes out of the window. Also, there is no memory of previous messages for newly connected clients, other than the often used 'last message'. In any case, having an opportunity to get this kind of feature quickly becomes a 'must have' when you have experienced the convenience that it gives you. Now, I am not arguing the Message Queues should do that because that is perhaps something that queues were not really designed to do. We are approaching the other side of this blog entry with this kind of requirement.

HTTP Messaging


A few years ago, we at my current job implemented something that can be described as a message queue. It is not a real message queue but it can be used for the exact same communication patterns. We implemented it with HTTP, utilizing Atom feeds for updates, with a RESTful interface.

A client can simply query for updates for the events that they are interested in. If there are new events, or updates to 'resources', you get an Atom feed as a reply. By default you may get the oldest events stored on the server, and you can browse the Atom feed for more events as you need them, or you can just zoom forward and grab the latest messages. You can tell the server to send only updates after a certain point, all of which you can track from the Atom feeds. You simply save the reference where you are in consuming the events at the client. This way, the server tracks absolutely nothing - the client decides what messages it considers consumed. This means that at any given time, you can just roll back and re-consume the same events if you like.

Because it is so simple to implement, it is also very reliable. All HTTP GETs are idempotent - can be re-tried, and you don't need transactions. You can implement transaction semantics quite easily by yourself. Say you need reliable messaging; from client's point of view this just means moving the reference point in the Atom feed once you have successfully consumed an Atom entry. If something failed, you can just come back and retry starting from the point you were at before. This is easy to implement, and you can make really sturdy consumers with simple rules.

There is no 'protocol', other than HTTP GET, PUT, POST, perhaps DELETE. You want events; do a GET for any combination of events you want. You want to publish events, just issues PUT, or POST for resource updates. You may also DELETE resources. All the events are Atom-wrapped. Simple.

I am talking about 'resources' here as liberally as I am talking about messages or events. This kind of server implementation can model things as documents or events and they can be consumed in the same manner.

The Bottom Line


For my requirements, HTTP/Atom feeding is a really nice solution. It is doable with MQs but it's not worth the hassle. The simplicity of HTTP/Atom approach is at the core of it all; no client issues, can be made reliable, browseable/queryable event feeds, scaling with typical web techniques.

No comments: