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

Portail informatique

RabbitMQ Lab — Tutorial

Foreword: 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.

We use RabbitMQ broker in Docker 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 Docker container for RabbitMQ:

$ docker pull rabbitmq:4.0.0-management

Here follows some explanations about the commands to launch and control the RabbitMQ server (a.k.a. broker). Please refer to these short explanations when you want to know more when you read the JAVA code excerpts and the scripts:

  • The server is launched with the Docker command:
    $ docker run -itd --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:4.0.0-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.0.0-management in order to include the management plugin.
  • The container just launched includes also a shell command (bash). Therefore, the server is controlled with the Docker command:
    $ docker exec rabbitmq rabbitmqctl <args>
    The utilitary 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 Docker container is remove using the following commands:
    $ docker stop rabbitmq $ docker rm rabbitmq

Check and configure JAVA

Check that you use JAVA version 17 or greater and that the JAVA_HOME shell variable is correctly set:
$ java -version openjdk version "21.0.4" 2024-07-16 OpenJDK Runtime Environment (build 21.0.4+7-Ubuntu-1ubuntu224.04) OpenJDK 64-Bit Server VM (build 21.0.4+7-Ubuntu-1ubuntu224.04, mixed mode, sharing) $ mvn -version Apache Maven 3.9.7 (8b094c9513efc1b9ce2d952b3b9c8eaedaf8cbf0) Maven home: /opt/maven Java version: 21.0.4, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64 Default locale: fr_FR, platform encoding: UTF-8 OS name: "linux", version: "6.8.0-45-generic", arch: "amd64", family: "unix" $ which java /usr/bin/java $ ls -l /usr/bin/java lrwxrwxrwx 1 root root 22 août 12 2022 /usr/bin/java -> /etc/alternatives/java $ ls -l /etc/alternatives/java lrwxrwxrwx 1 root root 43 avril 22 21:35 /etc/alternatives/java -> /usr/lib/jvm/java-21-openjdk-amd64/bin/java $ echo "export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64/" >> ~/.bashrc $ source ~/.bashrc $ echo $JAVA_HOME /usr/lib/jvm/java-21-openjdk-amd64

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 Maven modules. The source code of the tutorials is provided in Maven modules in the project csc-mw-examples on the platform https://gitlabense.imtbs-tsp.eu.

If you cannot or do not want to use Git, the examples are also available in this archive. Only when using the archive, you should proceed as follows:
$ wget http://www-inf.telecom-sudparis.eu/COURS/CSC-MW-Software-Installations/Examples/examples-rabbitmq.tgz $ mkdir ExemplesRabbitMQ $ cd ExemplesRabbitMQ $ tar xfz ../examples-rabbitmq.tgz $ ls -1 RabbitMQ-Tutorial-Step1 RabbitMQ-Tutorial-Step2 RabbitMQ-Tutorial-Step3 RabbitMQ-Tutorial-Step4 RabbitMQ-Tutorial-Step5 RabbitMQ-Tutorial-Step6

Before starting the tutorial, compile all the examples.

$ cd csc-mw-examples/ExamplesRabbitMQ/ $ for d in RabbitMQ-Tutorial-Step?; do (cd $d; mvn clean install); done ... # may be quite long 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.

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 server:

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

Exceptionnaly, i.e. not usually, producers can connect to queues and directly deposit messages into queues, i.e. without the messages going through an exchange. This functionality is demonstrated into Step 1 of the RabbitMQ tutorial:

see the Web page of Step 1, and the corresponding directory RabbitMQ-Tutorial-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:

$ cd RabbitMQ-Tutorial-Step1 $ ./run.sh --xterm # or ./run.sh for all in the same terminal ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running eu.telecomsudparis.rabbitmq.tutorial.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 now propose to comment the last two lines, which stop and remove the container, and to execute again ./run.sh. Next, by executing the command "docker exec rabbitmq rabbitmqctl list_queues", let us now observe that the queue hello that has been created still exists in the server even if there is no more producer or consumer. This is why we preventively stop and remove the container in all our scenarios.

The code of the example is in directory src/main/java/eu/telecomsudparis/rabbitmq/tutorial and the code of the test is in directory src/test/java/eu/telecomsudparis/rabbitmq/tutorial. You can also import the Maven project in your favourite JAVA IDE, e.g. Eclipse, for browsing the code of the example. In addition, observe that the test is an integration test because the name of the classe ends with IT, namely ScenarioIT.

NB: generally speaking, in case of problem "Connection refused" when executing a scenario, firstly, check that you use the docker container that includes the plugin rabbitmq_management, secondly, try a greater value in the sleep 10 command in shell script run.sh: currently, we assume that the brocker in the container starts in less that 10s.

Next, the authors of the RabbitMQ tutorial present a first list of interesting properties of queues in order to answer the following questions: How to use a queue as a backlog of work, i.e. of messages to treat, and to implement round-robin dispatching with several consumers attached to the shared queue? How to keep a sequence of messages in the queue until the consumer sends an acknowledgement message? How to make sure that even if the consumer dies messages are not lost as they can be persisted by the broker? And, how to organize fair dispatching with a prefetch countproducer? All these questions are the subject of Step 2 of the RabbitMQ tutorial:

Move to directory RabbitMQ-Tutorial-Step2. 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 prefetch countproducer, 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:

$ cd ...../RabbitMQ-Tutorial-Step2 $ ./run.sh --xterm # or ./run.sh for all in the same terminal ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running eu.telecomsudparis.rabbitmq.tutorial.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

The code of the example is in directory src/main/java/eu/telecomsudparis/rabbitmq/tutorial and the code of the test is in directory src/test/java/eu/telecomsudparis/rabbitmq/tutorial. You can also import the Maven project in your favourite JAVA IDE, e.g. Eclipse, for browsing the code of the example. In addition, observe that the test is an integration test because the name of the classe ends with IT, namely ScenarioIT.

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".

Move to directory RabbitMQ-Tutorial-Step3. 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:

$ cd ...../RabbitMQ-Tutorial-Step3 $ ./run.sh --xterm # or ./run.sh for all in the same terminal ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running eu.telecomsudparis.rabbitmq.tutorial.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

The code of the example is in directory src/main/java/eu/telecomsudparis/rabbitmq/tutorial and the code of the test is in directory src/test/java/eu/telecomsudparis/rabbitmq/tutorial. You can also import the Maven project in your favourite JAVA IDE, e.g. Eclipse, for browsing the code of the example. In addition, observe that the test is an integration test because the name of the classe ends with IT, namely ScenarioIT.

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

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

Move to directory RabbitMQ-Tutorial-Step4. 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:

$ cd ...../RabbitMQ-Tutorial-Step4 $ ./run.sh --xterm # or ./run.sh for all in the same terminal ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running eu.telecomsudparis.rabbitmq.tutorial.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 directory src/main/java/eu/telecomsudparis/rabbitmq/tutorial and the code of the test is in directory src/test/java/eu/telecomsudparis/rabbitmq/tutorial. You can also import the Maven project in your favourite JAVA IDE, e.g. Eclipse, for browsing the code of the example.

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.

Move to directory RabbitMQ-Tutorial-Step5. 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:

$ cd ...../RabbitMQ-Tutorial-Step5 $ ./run.sh --xterm # or ./run.sh for all in the same terminal ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running eu.telecomsudparis.rabbitmq.tutorial.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 directory src/main/java/eu/telecomsudparis/rabbitmq/tutorial and the code of the test is in directory src/test/java/eu/telecomsudparis/rabbitmq/tutorial. You can also import the Maven project in your favourite JAVA IDE, e.g. Eclipse, for browsing the code of the example.

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

A typical question when event-based programming with the AMQP protocol is the following one: Is it possible to "emulate" the synchronous RPC (Remote Procedure Call) paradigm with event-based asynchronous AMQP?

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

Here follows the picture of the architecture for this step.

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

$ cd ...../RabbitMQ-Tutorial-Step6 $ ./run.sh --xterm # or ./run.sh for all in the same terminal ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running eu.telecomsudparis.rabbitmq.tutorial.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

The code of the example is in directory src/main/java/eu/telecomsudparis/rabbitmq/tutorial and the code of the test is in directory src/test/java/eu/telecomsudparis/rabbitmq/tutorial. You can also import the Maven project in your favourite JAVA IDE, e.g. Eclipse, for browsing the code of the example.

Contrary to what is proposed into 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.