4
头图
To facilitate better communication, you can follow the public : 160f0e827d9912 Java class representative , one update every day, waiting for you!

3 Publish/Subscribe (Publish/Subscribe)

In on a , we have created a work queue. Its purpose is to distribute each task to only one worker. In this section, we will change the gameplay: we deliver a message so that all consumers can receive it. This model is called Publish/Subscribe (Publish/Subscribe).

To demonstrate this pattern, we will build a logging system. It contains two applications-the first sends log messages, and the second receives and prints log messages.

In our logging system, each receiving program in operation can receive the cancellation (Note to class representatives: the same message will be received by each consumer). In this way, we can have one recipient save the log to the hard disk; the other print the log on the screen.

In fact, the published log message will be broadcast to all receivers.

Exchanges

In the previous tutorial, we sent and received messages directly through the queue. Now it's time to introduce the complete message model in RabbitMQ.

Let's first make a brief review of the content introduced earlier:

  • producer (producer) is an application used to send messages.
  • queue is a message buffer.
  • consumer (consumer) is an application used to receive messages.

The core idea of the RabbitMQ message model is that the producer never sends messages directly to the queue. In fact, in most cases, the producer does not even know which queue the message will be distributed to.

Instead, the producer can only send messages to the exchange. The exchange is very simple, on the one hand it receives messages from the producer, on the other hand it pushes the messages to the queue. The exchange must know exactly what to do with the received message. Should the message be sent to a certain queue? Or is it sent to multiple queues? Or throw away the message? The routing type (exchange type) defines specific behavioral rules.

There are several types of routing: direct, topic, headers and fanout. Let’s take a look at the last one, fanout:

channel.exchangeDeclare("logs", "fanout");

The exchange of fanout types is very simple. As its name implies, it broadcasts the received message to all the queues it knows about. This is exactly the way our logging system needs it.

List all exchanges

In order to list all exchanges on the server, you can use the rabbitmqctl command:

sudo rabbitmqctl list_exchanges

Some exchanges with names like amq.* and default (unnamed) exchanges will appear in the list. These are created by default and there is no need to use them at this time.

exchange

In the previous tutorial, we did not know the existence of the exchange, but we can still send messages to the queue. This is because we have used the default exchange, which is identified by a null character ("").

Recall what we posted before:

channel.basicPublish("", "hello", null, message.getBytes());

The first parameter is the name of the exchange. A blank character indicates that the default exchange is used: if the message exists, the message will be routed to the queue through the specified routingKey.

Now we can send to the exchange with the specified name:

channel.basicPublish( "logs", "", null, message.getBytes());

Temporary queues

You may remember that we used named queues before (remember hello and task_queue?). Naming the queue is very important, because we need to let the worker listen to the corresponding queue. When you want to share the queue between producers and consumers, you must name the queue.

But this does not apply to our logging system. We need to listen to all log messages, not some of them. And we only care about the messages currently being sent, not historical messages. For this, we need to do two things:

First, every time we connect to RabbitMQ, we need a brand new queue. To this end, we can create a randomly named queue every time, or a better option is to let the server create a randomly named queue.

Secondly, once the queue has no consumers connected, it will be deleted automatically.

In the Java client, when we call the parameterless method queueDeclare(), a non-persistent, dedicated (class representative note: the queue is automatically deleted when the connection is closed), automatically deleted queue is created:

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

To learn more about exclusive flags and other attributes, see guide on queue .

At this time, the variable queueName is a randomly generated queue name string. Its value may be: amq.gen-JzTY20BRgKO-HjmUJj0wLg.

Bindings

We have created a fanout exchange, and now we need to tell the exchange which queue to send the message to. This relationship between exchange and queue is called binding .

channel.queueBind(queueName, "logs", "");

The above code will send the exchanged message named "logs" to our queue.

Listing bindings

Guess what tool you can use to list the binding relationships?

rabbitmqctl list_bindings

Code integration (Putting it all together)

The producer program that publishes the log message is not much different from the code in the previous tutorial. The biggest change is that now we send messages to the exchange named "logs", whereas before we sent them to the default anonymous exchange. When sending a message, you need to provide routingKey , but for fanout type exchanges, it will ignore the value of routingKey The following is the code EmitLog.java of the sending log program:

public class EmitLog {

  private static final String EXCHANGE_NAME = "logs";

  public static void main(String[] argv) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    try (Connection connection = factory.newConnection();
         Channel channel = connection.createChannel()) {
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

        String message = argv.length < 1 ? "info: Hello World!" :
                            String.join(" ", argv);

        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
        System.out.println(" [x] Sent '" + message + "'");
    }
  }
}

(EmitLog.java source file)

As you can see, when we established the connection, the exchange was declared. This step is very necessary

If there is no queue bound to the exchange, the message will be lost, but this does not affect our current application scenario. If there is no consumer, we can safely discard the message.

ReceiveLogs.java:

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;

public class ReceiveLogs {
  private static final String EXCHANGE_NAME = "logs";

  public static void main(String[] argv) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    Connection connection = factory.newConnection();
    Channel channel = connection.createChannel();

    channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
    String queueName = channel.queueDeclare().getQueue();
    channel.queueBind(queueName, EXCHANGE_NAME, "");

    System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

    DeliverCallback deliverCallback = (consumerTag, delivery) -> {
        String message = new String(delivery.getBody(), "UTF-8");
        System.out.println(" [x] Received '" + message + "'");
    };
    channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });
  }
}

(ReceiveLogs.java source file)

Compile as before.

javac -cp $CP EmitLog.java ReceiveLogs.java

If you want to save the log to a file, you can open a terminal and enter:

java -cp $CP ReceiveLogs > logs_from_rabbit.log

If you want to output the log on the screen, open a new terminal and run:

java -cp $CP ReceiveLogs

To issue the log type, enter:

java -cp $CP EmitLog

Use rabbitmqctl list_bindings to verify that the binding and queue created by the code are correct. After running two ReceiveLogs.java programs, you should see the following output:

sudo rabbitmqctl list_bindings
# => Listing bindings ...
# => logs    exchange        amq.gen-JzTY20BRgKO-HjmUJj0wLg  queue           []
# => logs    exchange        amq.gen-vso0PVvyiRIL2WoV3i48Yg  queue           []
# => ...done.

The explanation for this is also very simple: the messages exchanged by logs are sent to two queues whose names are generated by the server. This is exactly the result we expected.
To know how to listen to some (subset) of many messages, please refer to tutorial 4 .


Recommended reading
RabbitMQ Tutorial 1. "Hello World"

RabbitMQ Tutorial 2. Work Queue

Freemarker Tutorial (1)

downloaded attachment name is always garbled? You should read the RFC document!

MySQL priority queue in a simple way (the order by limit problem you will definitely step on)


The code word is not easy, welcome to like and share.
Search: [ Java class representative ], follow the official account, and get more Java dry goods in time.


Java课代表
640 声望1k 粉丝

JavaWeb一线开发,5年编程经验