RabbitMQ使用教程(四):发布/订阅模式—Publish/Subscribe

一、发布/订阅模式说明

今天我们来学习一点新的东西,之前我们是将一个消息发送给了一个特定的消费者,今天的做法完全不同,不再发送给某一个消费者,而是将一个消息发送给多个消费者,这便是:发布/订阅模式。

我们将使用该模式来实现一个日志系统:一个程序产生日志,一个程序处理日志。

二、认识交换机

在之前的教程中,我们用邮局作了比喻,回顾一下我们之前的理解:

一个生产者是用户程序,发送消息

一个队列是存放消息的地方,可以理解为缓冲区

一个消费者用户程序,接收消息

关键问题在于之前忘了讲:生产者的消息并不是直接发送给队列的,生产者本身也不知道要把消息发给哪个队列,而是将消息发送给交换机,再由交换机发送给队列。
这里写图片描述

生产者的消息并不是直接发送给队列的,而是发送给交换机的,交换机再发给队列的。

交换机理解起来非常简单,它一端接收生产者发送过来的消息,一端将消息发送给队列。所以,当交换机接收到一个消息时,它必须作出相应的处理,是发给一个特定的队列?还是发给多个队列?还是丢弃…

处理的方式取决于交换机的类型!

这里有四种可用的交换机类型:direct、topic、headers、fanout

我们使用最后一种fanout,创建该类型的交换机:

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

fanout交换机,你很容易猜到它的作用:将接收到的消息广播给每一个队列。

三、无名交换机(默认)

在之前的教程中,我们根本不知道交换机的概念,但我们还是将消息成功地发送了出去。
回顾一下之前的代码:

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

注意第一个参数,我们填了”“,这个”“正是默认交换机的名称,它的作用是根据routingKey,发送消息。发现了routingKey:hello,便会把消息发送给hello队列。到这里我们已经明白:第一个参数是交换机名称,第二个参数是routingKey名称。

现在我们需要创建日志系统所需的交换机:

channel.basicPublish( "logs", "", null, message.getBytes());
四、临时队列

大家应该记得我们之前的队列:”hello”、”task_queue”,我们的每一个队列都有一个名称。没错,队列的名称很重要,当我们需要特定的消费者来处理特点队列中的消息,消费者就是根据队列名来找到队列的。但是这在我们的日志系统中不适用,消费者需要接收所有的消息,而不是一个一部分,另外我们也只对刚刚产生的消息进行处理,日志一旦产生,就应该及时记录或打印。为了做到这两点,我们需要做两件事:

  1. 无论何时连接到RabbitMQ的都是一个最新的、空的队列。我们可以创建一个随机命名的队列,或者让RabbitMQ帮我们随机命名。
  2. 一旦消费者断开连接,该队列就马上把删除(不再接受新的消息)

我们可以使用queueDeclare()方法实现上面两点,创建一个不持久、独占的、自动删除的、随机命名的队列。独占队列是指:可以私用的队列,指定某个特定的连接可用,连接断开就自动删除。随机命名如:amq.gen-JzTY20BRgKO-HjmUJj0wLg.

String queueName=channel.queueDeclare().getQueue();
五、队列绑定交换机

我们已经创建了一个fanout类型的交换机:”logs”,现在我们需要指定该交换机的消息需要发送的队列,这个指定关系称为绑定。
这里写图片描述

channel.queueBind(queueName, "logs", "");
六、代码实现

我们之前是使用的默认交换机,指定routingKey为队列名称的方式来发送消息。

现在我们使用了fanout类型的”logs”交换机,(临时)队列与交换机绑定。这时候就不要routingKey了。

EmitLog.java



import java.io.IOException;
import java.util.concurrent.TimeoutException;

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

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

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
        for (int i = 1; i <=10; i++) {
            String message = "message" + i;
            channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());// arg0:不再使用""默认交换机,args1:有了交换机名,不再填对routingKey名
        }
        channel.close();
        connection.close();
    }

}

ReceiveLog1.java


import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

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

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
        String queueName = channel.queueDeclare().getQueue();// 产生一个不持久化、独占的、自动删除的、随机命名的队列
        channel.queueBind(queueName, EXCHANGE_NAME, "");
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
                    throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println(message);
            }
        };
        channel.basicConsume(queueName, true, consumer);
    }
}


首先运行接收端,再运行发送端,ReceiveLog2.java与ReceiveLog1.java的代码大同小异,这里建议使用IDEA,可以很直观的看到结果。因为,强大的IDEA不会有console覆盖问题。

运行结果:

这里写图片描述

这里写图片描述

参考命令:

列出所有交换机:

rabbitmqctl list_exchanges

列出所有绑定关系:

rabbitmqctl list_bindings

猜你喜欢

转载自blog.csdn.net/qq_35890572/article/details/81906268