Clojure and Rabbit MQ
Clojure and Rabbit MQ
An article from
Clojure in Action
EARLY ACCESS EDITION
Amit Rathore MEAP Release: November 2009 Softbound print: Early 2011 | 475 pages ISBN: 9781935182597
This article is taken from the book Clojure in Action. The author explains the AMPQ protocol basics and shows how to send and receive messages over RabbitMQ. Tweet this button! (instructions here)
Get 35% off any version of Clojure in Action with the checkout code fcc35. Offer is only valid through www.manning.com.
In this article, were going to write code for some basic communication tasks between Clojure processes and RabbitMQ. RabbitMQ uses the AMQP protocol, so a basic understanding of that protocol will help you get the most out of RabbitMQ. Therefore, our first stop will be to review essential elements of AMQP. Well then use the Java client library to send and receive messages over RabbitMQ. Finally, we'll create a convenient abstraction over incoming RabbitMQ messages for our programs that need to process messages asynchronously.
AMQP basics
Lets look at a few key concepts of RabbitMQ, which are reflective of the AMQP protocol underneath. Specifically, it is important to understand the basic concepts of the message queue, the exchange, and the routing key. Once these topics are understood, it becomes quite easy to understand the message flow and how message passing should be configured in a given application.
For Source Code, Sample Chapters, the Author Forum and other resources, go to http://www.manning.com/rathore/
Message queue
A message queue stores messages in memory or on a disk and delivers these to various consumers (usually) in the order they come in. Message queues can be created to have a varying set of propertiesprivate or shared, durable or not, permanent or temporary. The right combination of these properties can result in the various commonly used patterns of message queuesstore and forward, temporary reply queue, and the classic publish/subscribe model. Message queues can be named so that clients can refer to the specific ones they created during the routing configuration process.
Exchange
The exchange is a sort of clearinghouse that accepts the incoming messages and then, based on certain specified criteria, routes them to specific message queues. The criteria are called bindings and are specified by the clients themselves. AMQP defines a few exchange types, such as direct and fanout exchanges. By naming both exchanges and queues, clients can configure them to work with each other.
Routing key
An exchange can route messages to message-queues based on a variety of message properties. The most common way, though, is dependent on a single property called the routing key. It can be thought of as a virtual address that the exchange can use to route messages to the queue. Now that weve seen the fundamental constructs of AMQP, were ready to start playing with a server that implements it. In the next section, well begin this using RabbitMQ.
Connecting to RabbitMQ
Well now see how to connect to a RabbitMQ server. Further, well abstract away the idea of a connection to the server by creating a var for that purpose. First off, heres the code to create a new connection: (ns chapter14-rabbitmq (:import (com.rabbitmq.client ConnectionParameters ConnectionFactory QueueingConsumer))) (defn new-connection [q-host q-username q-password] (let [params (doto (ConnectionParameters.) (.setVirtualHost "/") (.setUsername q-username) (.setPassword q-password))] (.newConnection (ConnectionFactory. params) q-host))) Now for the varlets create one called *rabbitmq-connection* as follows: (def *rabbit-connection*) We can bind this to a call to new-connection whenever we need to do something with RabbitMQ. In order to make this easy, we can create a macro to do it for us. One might call such a macro with-rabbit and it might look like this: (defmacro with-rabbit [[mq-host mq-username mq-password] & exprs] `(with-open [connection# (new-connection ~mq-host ~mq-username ~mq-password)] (binding [*rabbit-connection* connection#] (do ~@exprs)))) Note that, by using the with-open macro, the connection held in *rabbit-connection* gets closed when we exit the with-rabbit form. Now, with the with-rabbit macro in hand, were ready to write more code to do things like sending messages to the server and receiving messages from it. The next couple of sections show how to do just that.
For Source Code, Sample Chapters, the Author Forum and other resources, go to http://www.manning.com/rathore/
#B
#C
Whats happening here is that we create an instance of QueueingConsumer, which is a class provided by the RabbitMQ client library. We then declare the queue-name we were passed in, following which we attach the
consumer to the queue-name via the call to basicConsume. The delivery-from function calls the nextDelivery method on the consumer object, and that is a blocking call. The execution proceeds only when
the consumer receives something from RabbitMQ, and then all we do is acknowledge the message by calling
For Source Code, Sample Chapters, the Author Forum and other resources, go to http://www.manning.com/rathore/
(println "Waiting...") (with-rabbit ["localhost" "guest" "guest"] (println (next-message-from "chapter14-test"))) This will cause the program to block (since next-message-from blocks until a message is delivered to it from the RabbitMQ server) and, in order to unblock it and see that it prints the message to the console, we have to send it a message. This following program, which can also be run as a script, does just that: (ns chapter14-sender (:use chapter14-rabbitmq)) (println "Sending...") (with-rabbit ["localhost" "guest" "guest"] (send-message "chapter14-test" "chapter 14 test method")) (println "done!") If you run the above two programs in two separate shells (run the receiver first!), youll see our two functions
send-message and next-message-from in action. Now that weve got basic communication going over
RabbitMQ, lets write a version of the receiver program that can handle multiple messages from the queue.
For Source Code, Sample Chapters, the Author Forum and other resources, go to http://www.manning.com/rathore/
(recur (next-message-from "chapter14-test")))) (with-rabbit ["localhost" "guest" "guest"] (println "Waiting for messages...") (handle-multiple-messages println)) With this higher-order function called handle-multiple-messages, we can now do whatever we please with an incoming stream of messages. Indeed, if you run several instances of this program in parallel and send messages using the same sender program, youll see RabbitMQ deliver messages to each in a roughly round-robin manner. As said earlier, this can form the basis of a compute cluster of some kind. One thing to note is that our next-message-from function is quite inefficient. It creates a new channel each time it is called. If we know were going to process multiple messages, we really should improve this state of affairs. Indeed, while were at it, we should recognize the fact that an incoming sequence of messages can be modeled as a Clojure sequence. Well do this in the next section.
#A
Note that lazy-message-seq is a helper function, which is private to this namespace. The message-seq function is the one that we can use in our programs. Lets write a version of multiple message handler that uses it: (ns chapter14-receiver-multiple3 (:use chapter14-rabbitmq)) (defn handle-multiple-messages [handler] (doseq [message (message-seq "chapter14-test")] (handler message))) (with-rabbit ["localhost" "guest" "guest"] (println "Waiting for messages...") (handle-multiple-messages println)) There are several advantages of this approach. The first is that its more efficient since were no longer creating a new channel (and consumer) for each message. Perhaps more importantly, since message-seq is a real Clojure sequence, we can use the full Clojure sequence library on it. The example above shows the usage of doseq, but we can now map across it, filter out only those messages that we like, and so on. Heres an example where we only print the messages in pairs: (ns chapter14-receiver-multiple4 (:use chapter14-rabbitmq clojure.contrib.str-utils)) (defn print-two-messages [messages] (println (str-join "::" messages))) (with-rabbit ["localhost" "guest" "guest"] (println "Waiting for messages...")
For Source Code, Sample Chapters, the Author Forum and other resources, go to http://www.manning.com/rathore/
(let [message-pairs (partition 2 (message-seq "chapter14-test"))] (doseq [message-pair message-pairs] (print-two-messages message-pair)))) We can test this by sending it messages using our sender program, as usual. The output of a test run might look like the following: Waiting for messages... chapter 14 test method::chapter 14 test method chapter 14 test method::chapter 14 test method The fact that we can now bring the full power of the Clojure sequence library to bear on a series of messages from RabbitMQ allows us to write idiomatic Clojure programs. No function outside the scope of message-seq needs to know that the sequence is lazily being fed by a RabbitMQ server, somewhere on the network.
Summary
We can use the code we wrote in this article as a basis for real applications to handle events in an asynchronous manner. Further, we can expand our event processing capacity simply by starting up more instances of our handlers. This idea forms the basis of using messaging to scale up applications.
Here are some other Manning titles you might be interested in:
For Source Code, Sample Chapters, the Author Forum and other resources, go to http://www.manning.com/rathore/
Michael Fogus and Chris Houser MEAP Release: January 2010 Softbound print: December 2010 | 400 pages ISBN: 9781935182641
Tomas Petricek with Jon Skeet December 2009 | 560 pages ISBN: 9781933988924
Scala in Action
EARLY ACCESS EDITION
Nilanjan Raychaudhuri MEAP Began: March 2010 Softbound print: Spring 2011 | 525 pages ISBN: 9781935182757
For Source Code, Sample Chapters, the Author Forum and other resources, go to http://www.manning.com/rathore/