RabbitMQ(三)——发布订阅模式(引入交换机的概念)

前言

上一篇博客中我们创建了一个工作队列,工作队列中的每一条消息都被发送给一个指定的消费者,这篇博客我们会玩一个完全不一样的——将同一个消息发送给多个消费者(这个就是我们传说中的发布订阅模式)。

官网的教程第三篇中,描述了一个日志场景——一个生产者发送日志信息,两个消费者接受日志信息,其中一个消费者打印日志,另一个消费者需要做日志的持久化。

In our logging system every running copy of the receiver program will get the messages. That way we'll be able to run one receiver and direct the logs to disk; and at the same time we'll be able to run another receiver and see the logs on the screen.

Essentially, published log messages are going to be broadcast to all the receivers.

实际上也就是发布的所有日志信息会被广播给所有消费者。

交换机

前几篇博客中,我们代码中好像是直接将消息发送给了队列,这里我们介绍一下RabbitMQ完整的消息模式。

RabbitMQ中,生产者从不直接将消息发送给队列,实际上,消费者都不知道消息将要发送给那个队列。RabbitMQ中生产者只能将消息发送给exchange。交换机再将消息发送给消息队列。不同的交换机类型决定了消息分发的方式。

上篇博客中并没有指定交换机,但是生产者却直接发送消息到了消息队列中,这是因为有默认交换机的存在。

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

第一个参数就是指定交换机名称,但是这里直接送了一个空串,RabbitMQ这个时候会使用系统默认的交换机

交换机有四种类型,这里不展开介绍了,可以看一下大牛的博客:传送门——RabbitMQ中交换机的类型

这篇博客的实例我们使用fanout类型的交换机,fanout本身就是扇形的意思,这个就是用于广播发送消息的交换机。

临时队列

上一篇博客中,我们主动建立了一个消息队列,通过指定消息队列名称之后,消费者才能正确消费消息。但是由于有轮询机制在,不同的消费者其实收到的只是消息队列里面的部分消息,并不是全部。这个并不能应用于日志系统,日志系统中的两个消费者都需要获得队列中的所有消息。为解决这个问题,对消费者来说,我们需要准备两样东西。

1、当消费者连接到RabbitMQ的时候,我们需要一个全新的队列。

2、当消费者断开连接的时候,RabbitMQ就需要自动删除队列。

在代码中,声明队列的时候,我们不指定任何参数,就是声明一个不支持持久化,自动删除的,排他的队列。

绑定

队列的问题解决了,现在就是需要将exchange和queue进行绑定。这步操作依旧是在消费者中完成,只需要调用如下代码即可。

channel.queueBind(queueName,EXCHANGE_NAME,"");//消费端将交换机和队列进行绑定

完整代码

ExchangeSender

package com.learn.rabbitmq.exchange;

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

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

/**
 * author:liman
 * createtime:2019/10/11
 */
public class ExchangeSender {

    private static final String QUEUE_NAME = "exchange_queue";

    private static final String EXCHANGE_NAME="logs";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();

        Channel channel = connection.createChannel();
        channel.exchangeDeclare("logs","fanout");

        for(int i = 0;i<10;i++){
            String message = "info:hello world!"+i;
            channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes("UTF-8"));
        }
        System.out.println("message send success!");
    }

}

 ExchangeReceive

package com.learn.rabbitmq.exchange;

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

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

/**
 * author:liman
 * createtime:2019/10/11
 */
public class ExchangeReceive {

    private static final String QUEUE_NAME="hello_queue";

    private static final String EXCHANGE_NAME="logs";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection=connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");//消费端声明交换机
        String queueName=channel.queueDeclare().getQueue();//申明一个临时队列
        channel.queueBind(queueName,EXCHANGE_NAME,"");//消费端将交换机和队列进行绑定
        System.out.println("wait for message");

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

}

运行结果:

 启动两个消费者,如果两个消费者收到的消息均一致,即为想要的结果

总结

到这里算是明白了所有的RabbitMQ内部消息模型。

参考资料:

同时将消息发送给多个消费者

发布了129 篇原创文章 · 获赞 37 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/liman65727/article/details/102549371