Module CSC5002—ASR6: Middleware and software architecture for distributed applications

Portail informatique

Learn about AMQP with RabbitMQ

Learn the AMQP concepts through Java programming examples using RabbitMQ.

Foreword 1: When you first read the different steps, we suggest that you do not study the details, i.e. that you do not open the “details” tags.

Foreword 2: At the end of this page (Step 4), there is a list of questions for checking and leveraging your understanding of RabbitMQ. We think that this is the right time to open and read the ‘tips’.

We use RabbitMQ broker in Podman containers. In addition, since we use Maven, the client library is going to be installed through a Maven dependency.

Launching the RabbitMQ broker

Get the Podman container for RabbitMQ:

$ podman run -itd --name rabbitmq -p 5672:5672 -p 15672:15672 docker.io/library/rabbitmq:4.1.4-management

Here follows some explanations about the commands to launch and control the RabbitMQ server (a.k.a. broker). Since we use scripts to execute the example scenarios, these explanations are considered as details. Please refer to these short explanations when you want to know more when you read the scripts: (Details 1)

  • The broker is launched with the Podman command:
    $ podman run -itd --name rabbitmq -p 5672:5672 -p 15672:15672 docker.io/library/rabbitmq:4.1.4-management
    The port 5672 is the port for the access to the broker and the port 15672 is the port for the access to the management plugin of the broker.
    We use the container 4.1.4-management in order to include the management plugin.
  • The container just launched includes also a shell command (bash). Therefore, the broker is controlled with the Podman command:
    $ podman exec rabbitmq rabbitmqctl <args>
    The utility command rabbitmqctl (Web page here) is a shell script, with the following arguments:
    • "status": to display broker status information such as the running applications, RabbitMQ and Erlang versions, OS name, memory and file descriptor statistics,
    • "stop": to stop the Erlang node on which the RabbitMQ broker is running,
    • "reset": to return the RabbitMQ broker to its virgin state (to be done after stopping the RabbitMQ broker with stop-app),
    • "stop-app": to stop the RabbitMQ application (the broker), leaving the Erlang node running,
    • "start-app": to start the RabbitMQ application (the broker) on the Erlang node,
    • "list_queues": to display queues details such as their names.
    • "list_exchanges": to display exchanges details such as their names.
    • "list_bindings": to display bindings details such as the routing keys.
  • The RabbitMQ broker is stopped and the Podman container is remove using the following commands:
    $ podman stop rabbitmq $ podman rm rabbitmq

Get the source code of the tutorials prepared by the RabbitMQ team

In this lab, we use the tutorial steps prepared by the RabbitMQ team. We have gathered and “mavenized” the code in a Maven module. The source code of the tutorials is in the csc-mw-examples GitLabEnsee project in directory CodeForLearning/Learn-AMQP-RabbitMQ.

Before starting the tutorial, compile all the examples.

$ cd csc-mw-examples/CodeForLearning/Learn-AMQP-RabbitMQ/ $ mvn clean install ... # longer the first time you compile the examples [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ ...

Follow the tutorial prepared by the RabbitMQ team

The RabbitMQ tutorial contains seven steps. We reorganise it to have less of them. This is done by introducing the concepts of the first steps in this page. As a consequence, the corresponding text is from RabbitMQ Web pages and is in italics.

Producer, exchange, consumer, queue, routing key, and binding key

The first concepts of producer, exchange, consumer, queue, routing key, and binding key are depicted in the following figure. The text following the figure presents these concepts.

Figure 1: AMQP concepts.

[Extracted and adapted from the RabbitMQ tutorial page.] Producing means sending. A program that sends messages is a producer. A producer sends messages to an exchange. Consuming means receiving from a queue. A consumer is a program that mostly waits to receive messages.

Producers send messages to exchanges. An exchange is a matching and routing engine: It inspects messages (headers, and more especially what is called, in the AMQP vocabulary, a routing key), and decides how to forward these notifications to message queues (the decision being made using subscription filter's data, and more especially what is called, in the AMQP vocabulary, a binding key) that is provided by the consumer. We will see that they are several types of exchanges, i.e. different matching and routing engines.

Although messages flow through RabbitMQ and your applications, they can only be stored inside a queue. A queue is only limited by the host's memory and disk limits. It is essentially a large message buffer. Many producers can send messages that go to one queue, and many consumers can try to receive data from one queue.

Note that the producer, consumer, and broker do not have to reside on the same host. Indeed in most applications they don't. An application can be both a producer and consumer, too.

In all the JAVA code excerpts, clients, either producers or consumers, open a connection to the RabbitMQ broker (Note the use of the try-with-resources JAVA statement):

ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) { }

In exceptional cases—that is, not as a general rule—producers may connect to queues and submit messages directly to them, without those messages passing through an exchange. This functionality is demonstrated into Step 1 of the RabbitMQ tutorial: (Details 2)

see the Web page of Step 1, and the corresponding package mw.learn.amqp.rabbitmq.step1.

Here follows the picture of the architecture for this step.

Figure 2: Producer, consumer, queue (RabbitMQ tutorial step 1).

At your convenience, and optionally , you can read these Web pages and run the corresponding code examples:

$ ./run_step1_podman.sh --xterm # or ./run_step1_podman.sh for all displays on the same terminal ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running mw.learn.amqp.rabbitmq.step1.TestScenario b2a7598c5ba35fed8962265a5ead6ac9609f5f161cbe766dd08985c7bfdfe8ee Timeout: 60.0 seconds ... Listing queues for vhost / ... [*] Waiting for messages. To exit press CTRL+C [x] Sent 'Hello World!' [x] Received 'Hello World!' Timeout: 60.0 seconds ... Listing queues for vhost / ... name messages hello 0 rabbitmq rabbitmq

We will now comment out the last two lines, which stop and delete the container, and then rerun the ./run_step1_podman.sh command. By running the command “podman exec rabbitmq rabbitmqctl list_queues,” let’s now observe that the hello queue that was created still exists on the broker, even though there are no longer any producers or consumers. This is why we stop and delete the container as a precaution in all our scenarios.

We suggest that you import the Maven project in your favourite JAVA IDE, e.g. Eclipse, for browsing the code of the example. Let us observe that the test is an integration test because the name of the class ends with IT, namely ScenarioIT.

Note: Generally speaking, if you encounter a “Connection refused” error when running a scenario, first check that you are using the Podman container that includes the rabbitmq_management plugin, then try increasing the value of the sleep 10 command in the ./run_step?_podman.sh shell script: At present, we assume that the broker within the container starts up in less than 10 seconds.

The authors of the RabbitMQ tutorial then present an initial list of useful queue properties to address the following questions: How can a queue be used as a work queue—that is, a collection of messages to be processed—and how can a “round-robin” distribution be implemented with multiple consumers connected to the shared queue? How can a sequence of messages be maintained in the queue until the consumer sends an acknowledgment message? How can we ensure that, even if the consumer fails, messages are not lost since they can be persisted by the broker? And how can we organize fair distribution using a consumer’s prefetch counter? All these questions are the subject of Step 2 of the RabbitMQ tutorial:

(Details 3)

Learn the next set of AMQP concepts (round-robing dispatching, durable message and message acknowledgment, message durability with durable queue and persistent message, fair dispatching with a consumer prefetch count, consumer, and queue) with the corresponding RabbitMQ tutorial page.

Here follows the picture of the architecture for this step.

Figure 3: Exchange of type fanout and binding (RabbitMQ tutorial step 3)

At the end of your reading of the tutorial page, you can execute the example as follows (The code of the example is in package mw.learn.amqp.rabbitmq.step2):

$ ./run_step2_podman.sh --xterm # or ./run_step2_podman.sh for all displays on the same terminal ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running mw.learn.amqp.rabbitmq.step2.TestScenario b2a7598c5ba35fed8962265a5ead6ac9609f5f161cbe766dd08985c7bfdfe8ee Timeout: 60.0 seconds ... [*] Waiting for messages. To exit press CTRL+C [*] Waiting for messages. To exit press CTRL+C [x] Sent 'wait one second .' [x] Sent 'wait two seconds..' [x] Sent 'wait three seconds...' [x] Sent 'wait four seconds....' [x] Sent 'wait five seconds.....' [x] Received 'wait one second .' [x] Received 'wait two seconds..' [x] Done [x] Received 'wait three seconds...' [x] Done [x] Received 'wait four seconds....' [x] Done [x] Received 'wait five seconds.....' [x] Done [x] Done rabbitmq rabbitmq

RabbitMQ tutorial, step 3 (exchange [of type fanout], binding, and temporary queue)

This step (Step 3) and the next two steps (Steps 4 and 5) of the RabbitMQ tutorial are the most important steps in a first understanding of event-based programming with the AMQP protocol, and more especially with RabbitMQ.

Let us start with a first type of exchange: "fanout".

Learn the next set of AMQP concepts (exchange [of type fanout], binding, and temporary queue) with the corresponding RabbitMQ tutorial page.

Here follows the picture of the architecture for this step.

Figure 4: Exchange of type “fanout” and binding (RabbitMQ tutorial step 3)

At the end of your reading of the tutorial page, you can execute the example as follows:

$ ./run_step3_podman.sh --xterm # or ./run_step3_podman.sh for all displays on the same terminal ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running mw.learn.amqp.rabbitmq.step3.TestScenario b2a7598c5ba35fed8962265a5ead6ac9609f5f161cbe766dd08985c7bfdfe8ee Timeout: 60.0 seconds ... [*] Waiting for messages. To exit press CTRL+C [*] Waiting for messages. To exit press CTRL+C [x] Sent 'message one' [x] Sent 'message two' [x] Sent 'message three' [x] Sent 'message four' [x] Sent 'message five' [x] Received 'message one' [x] Received 'message two' [x] Received 'message three' [x] Received 'message four' [x] Received 'message five' [x] Received 'message one' [x] Received 'message two' [x] Received 'message three' [x] Received 'message four' [x] Received 'message five' rabbitmq rabbitmq

Note: Generally speaking, if you encounter a “Connection refused” error when running a scenario, first check that you are using the Podman container that includes the rabbitmq_management plugin, then try increasing the value of the sleep 10 command in the ./run_step?_podman.sh shell script: At present, we assume that the broker within the container starts up in less than 10 seconds.

The code of the example is in package mw.learn.amqp.rabbitmq.step3.

RabbitMQ, tutorial, step 4 (binding key, exchange of type direct, routing key)

Let us continue with a second type of exchange: “direct”.

Learn the next set of AMQP concepts (binding key, exchange of type direct, routing key) with the corresponding RabbitMQ tutorial page.

Here follows the picture of the architecture for this step.

Figure 5: Exchange of type “direct”, and binding and routing keys (RabbitMQ tutorial step 4)

At the end of your reading of the tutorial page, you can execute the example as follows:

$ ./run_step4_podman.sh --xterm # or ./run_step4_podman.sh for all displays on the same terminal ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running mw.learn.amqp.rabbitmq.step4.TestScenario b2a7598c5ba35fed8962265a5ead6ac9609f5f161cbe766dd08985c7bfdfe8ee [x] Consumer ReceiveLogsDirect2 binds queue amq.gen-VFfRMC-z7Cr0Rrf9Z7hjbA to exchange direct_logs with routing key info [x] Consumer ReceiveLogsDirect2 binds queue amq.gen-VFfRMC-z7Cr0Rrf9Z7hjbA to exchange direct_logs with routing key warning [x] Consumer ReceiveLogsDirect2 binds queue amq.gen-VFfRMC-z7Cr0Rrf9Z7hjbA to exchange direct_logs with routing key error [*] Consumer ReceiveLogsDirect2 is waiting for messages. To exit press CTRL+C [x] Consumer ReceiveLogsDirect1 binds queue amq.gen-nleNaKBjxFKONX8_Y8WFzA to exchange direct_logs with routing key error [*] Consumer ReceiveLogsDirect1 is waiting for messages. To exit press CTRL+C Timeout: 60.0 seconds ... ... [x] Sent 'info':'message one' [x] Consumer ReceiveLogsDirect2 received ' info':'message one' [x] Sent 'debug':'message two' [x] Sent 'error':'message three' [x] Consumer ReceiveLogsDirect2 received ' error':'message three' [x] Consumer ReceiveLogsDirect1 received ' error':'message three' [x] Sent 'info':'message four' [x] Consumer ReceiveLogsDirect2 received ' info':'message four' [x] Sent 'debug':'message five'

The code of the example is in package mw.learn.amqp.rabbitmq.step4.

RabbitMQ tutorial, step 5 (exchange of type topic, a word in a binding key, a star in a binding key, a hash in a binding key)

Let us continue with the third and final type of exchange: "topic". This matching and routing engine is the one that fully realize the topic-based event-based paradigm.

Learn the next set of AMQP concepts (exchange of type topic, a word in a binding key, a star in a binding key, a hash in a binding key) with the corresponding RabbitMQ tutorial page.

Here follows the picture of the architecture for this step.

Figure 6: Exchange of type “topic”, and wildcards (RabbitMQ tutorial step 5)

At the end of your reading of the tutorial page, you can execute the example as follows:

$ ./run_step5_podman.sh --xterm # or ./run_step5_podman.sh for all displays on the same terminal ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running mw.learn.amqp.rabbitmq.step5.TestScenario b2a7598c5ba35fed8962265a5ead6ac9609f5f161cbe766dd08985c7bfdfe8ee Timeout: 60.0 seconds ... [*] Waiting for messages. To exit press CTRL+C [*] Waiting for messages. To exit press CTRL+C [*] Waiting for messages. To exit press CTRL+C [x] Sent 'quick.orange.rabbit':'message one' [x] Sent 'lazy.orange.elephant':'message two' [x] Sent 'quick.orange.fox':'message three' [x] Sent 'lazy.brown.fox':'message four' [x] Sent 'lazy.pink.rabbit':'message five' [x] Sent 'lquick.brown.fox':'message six' [x] Sent 'orange':'message seven' [x] Sent 'quick.orange.male.rabbit':'message height' [x] Sent 'lazy.orange.male.rabbit':'message nine' [x] Received '0 quick.orange.rabbit':'message one' [x] Received '0 lazy.orange.elephant':'message two' [x] Received '0 quick.orange.fox':'message three' [x] Received '0 lazy.brown.fox':'message four' [x] Received '0 lazy.pink.rabbit':'message five' [x] Received '0 lquick.brown.fox':'message six' [x] Received '0 orange':'message seven' [x] Received '0 quick.orange.male.rabbit':'message height' [x] Received '0 lazy.orange.male.rabbit':'message nine' [x] Received '1 quick.orange.rabbit':'message one' [x] Received '1 lazy.orange.elephant':'message two' [x] Received '1 quick.orange.fox':'message three' [x] Received '2 quick.orange.rabbit':'message one' [x] Received '2 lazy.orange.elephant':'message two' [x] Received '2 lazy.brown.fox':'message four' [x] Received '2 lazy.pink.rabbit':'message five' [x] Received '2 lazy.orange.male.rabbit':'message nine' rabbitmq rabbitmq

The code of the example is in package mw.learn.amqp.rabbitmq.step5.

RabbitMQ tutorial, step 6 (How to realize RPC communication over topic-based DEBS with RabbitMQ)

A question that often arises when programming in an event-driven style with the AMQP protocol is this: is it possible to “emulate” the synchronous RPC (Remote Procedure Call) paradigm using the asynchronous, event-driven AMQP protocol?

The answer to this question is “Yes, we can!” and it is the subject of Step 6 of the RabbitMQ tutorial:

(Details 4)

Here follows the picture of the architecture for this step.

Figure 7: RPC with AMQP (RabbitMQ tutorial step 6)

Learn the last set of AMQP concepts (How to realize RPC communication over topic-based DEBS with RabbitMQ) with the corresponding RabbitMQ tutorial page.

The code of the example is in package mw.learn.amqp.rabbitmq.step6;.

Differently from what is proposed in the tutorial page, in our code, we propose three different versions:

  1. using the JAVA client library with the standard AMQP calls (this is the version that is presented in the tutorial page),
  2. using the RabbitMQ-specific class StringRpcServer,
  3. using the RabbitMQ-specific classes of the package com.rabbitmq.tools.jsonrpc.
For the sake of completeness, study the three implementations.
$ ./run_step6_1_podman.sh --xterm # or ./run_step6_1_podman.sh for all displays on the same terminal ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running mw.learn.amqp.rabbitmq.step6.TestScenario b2a7598c5ba35fed8962265a5ead6ac9609f5f161cbe766dd08985c7bfdfe8ee Timeout: 60.0 seconds ... completed with 3 plugins. [x] Requesting fib(0) [.] Got '0' [x] Requesting fib(1) [.] Got '1' [x] Requesting fib(2) [.] Got '1' [x] Requesting fib(3) [.] Got '2' [x] Requesting fib(4) [.] Got '3' [x] Requesting fib(5) [.] Got '5' [x] Requesting fib(6) [.] Got '8' rabbitmq rabbitmq

Questions after the discovery lab of RabbitMQ

Answer to the following questions by parsing the tutorial and searching the RabbitMQ Doc Web site. For some of the questions in the list, we assume that you have also open the “Details” tags of the discovery lab, and read and understood these contents.

When you have an initial draft of an answer, check by yourself with the “Solution” elements. Do not hesitate to ask for other explanations.

What is the effect of the execution of the two following statements?


channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.queueDeclare(QUEUE_NAME, false, false, false, null);

Declaring a queue is idempotent—the queue will only be created if it doesn't exist already.

What is the type of the content of a message?

The message content is a byte array, so you can encode whatever you like there.

What is the semantics of an acknowledgement?

In order to make sure a message is never lost, RabbitMQ supports message acknowledgments. An acknowledgement is sent back from the consumer to tell RabbitMQ broker that a particular message has been received, and that the broker is free to delete it.

If a consumer dies (the AMQP channel is closed, the AMQP connection is closed, or the TCP connection is lost) without sending an ack, the broker will understand that a message wasn't delivered and the broker will re-queue it. If there are other consumers online at the same time, it will then quickly redeliver it to another consumer. That way, you can be sure that no message is lost, even if the workers occasionally die.

What is the semantics of the assignement autoAck=false ? Does this concern the producer or the consumer?

Send a proper acknowledgment from the consumer, once the consumer is done with the treatment of a message.

How to make the sending of messages reliable, including when the broker fails?

When RabbitMQ broker quits or crashes, it will forget the queues and messages unless you tell it not to. Two things are required to make sure that messages aren't lost: we need to mark both the queue and messages as durable.

Marking messages as persistent doesn't fully guarantee that a message won't be lost. Although it tells RabbitMQ broker to save the message to disk, there is still a short time window when the broker has accepted a message and hasn't saved it yet. Also, the broker doesn't do fsync(2) for every message—it may be just saved to cache and not really written to the disk. If you need a stronger guarantee then you can use publisher confirms. See page “Consumer Acknowledgements and Publisher Confirms”. The following figure depicts in a continuum the reliability capabilities provided by RabbitMQ.

Figure 8: Continuum of the reliability capabilities provided by RabbitMQ

What is the semantics of the following two lines?


int prefetchCount = 1;
channel.basicQos(prefetchCount);

This tells RabbitMQ broker not to give more than one message to a consumer at a time. Or, in other words, don not dispatch a new message to a consumer until the consumer has processed and acknowledged the previous one. Instead, the broker can dispatch the message to the next consumer that is not still busy.

What are the (four value) properties of the queue created with the following instruction?


String queueName = channel.queueDeclare().getQueue();

We create a “non-durable” (the queue does not survive a broker restart), “exclusive” (used by only one connection and deleted when the connection closes), “autodelete” (deleted when the last consumer unsubscribes) queue with a “generated name”.

What happpens if one publishes to an exchange that has no queues bound to it?

The messages will be lost if no queue is bound to the exchange yet.

Is it legal to have several queues bound to the same exchange with the same binding key?

It is perfectly legal to bind multiple queues with the same binding key. In Step 4 of the tutorial, we could add a binding between X and Q1 with binding key “black”. In that case, the direct exchange will behave like fanout and will broadcast the message to all the matching queues. A message with routing key “black” will be delivered to both Q1 and Q2.

What is the maximum size of a routing key?

There can be as many words in the routing key as you like, up to the limit of 255 bytes.

What are the two wildcards (or meta-characters) of a binding key? Which semantics?

There are two important special cases for binding keys:
  • ‘x’ (star): can substitute for exactly one word;
  • ‘#’ (hash): can substitute for zero or more words.

How does one simulates an exchange of type fanout or of type direct with an exchange of type topic?

  • When a queue is bound with ‘#’ (hash) binding key, it will receive all the messages, regardless of the routing key—like in fanout exchange.
  • When meta-characters ‘*’ (star) and ‘#’ (hash) aren't used in bindings, the topic exchange will behave just like a direct one.

How does one specify that the content is of type JSON?

The AMQP 0-9-1 protocol predefines a set of 14 properties that go with a message. Property “contentType” is used to describe the mime-type of the encoding.
For instance, for the often used JSON encoding, it is a good practice to set this property to “application/json”.

What is the semantics of the property “correlationId” of a message?

Property “correlationId” is used in Step 6 of the tutorial. It is useful to correlate RPC responses with requests. The client that sends requests—i.e., by publishing the corresponding message—provides a unique value for every request. Later, when the client receives a message in the callback queue, it will look at this property, and based on that, it will be able to match a response with a request. If the client sees an unknown “correlationId” value, it may safely discard the message—it is not a reply to one of its requests. Usually, the broker extracts the value inserted in the request and uses it for the answer.