Learn about AMQP with 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’.
Launching the RabbitMQ broker
Get the Podman container for RabbitMQ:
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:
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.$ podman run -itd --name rabbitmq -p 5672:5672 -p 15672:15672 docker.io/library/rabbitmq:4.1.4-management
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:
The utility command rabbitmqctl (Web page here) is a shell script, with the following arguments:$ podman exec rabbitmq rabbitmqctl <args>
- "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.
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.
[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):
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.
At your convenience, and optionally , you can read these Web pages and run the corresponding code examples:
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.
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):
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.
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.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.
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.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.
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.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.
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:
- using the JAVA client library with the standard AMQP calls (this is the version that is presented in the tutorial page),
- using the RabbitMQ-specific class StringRpcServer,
- using the RabbitMQ-specific classes of the package com.rabbitmq.tools.jsonrpc.
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);
What is the type of the content of a message?
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?
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.
What is the semantics of the following two lines?
int prefetchCount = 1;
channel.basicQos(prefetchCount);
What are the (four value) properties of the queue created with the following instruction?
String queueName = channel.queueDeclare().getQueue();
What happpens if one publishes to an exchange that has no queues bound to it?
Is it legal to have several queues bound to the same exchange with the same binding key?
What is the maximum size of a routing key?
What are the two wildcards (or meta-characters) of a binding key? Which semantics?
- ‘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?
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?