RabbitMQ——4、发布与订阅

本文介绍了RabbitMQ的发布订阅模式,包括交换器的概念、类型及其使用,临时队列的特点,以及如何通过绑定将交换机与队列关联。重点讲解了Direct、Fanout、Topic和Header四种交换机类型,并提供了相关代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在前面的工作队列中我们创建了工作队列。工作队列背后的假设是每一项任务只会明确分配给一个工作者。

在本节中我们将学RabbitMQ的发布、订阅模式,即一条消息会同时投递所有订阅者。

一、Exchanges 交换器

RabbitMQ消息传递模型的核心思想是,生产者永远不会将任何消息直接发送到队列。实际上,通常生产者甚至不知道消息是否会被传递到任何队列。

在RabbitMQ中,生产者只能向exchange(交换器)发送消息。交换器负责接收生产者的消息,并将其转发到消费者。所以交换器本身需要知道具体的分发规则,例如是否要将消息添加到特定的队列?是否添加到多个队列?是否丢弃消息等等。

1.1 交换机类型(Exchange Type)

RabbitMQ中有多种交换机类型:

  1. Direct exchange(直连交换机):根据消息携带的路由键(routing key)将消息投递到对应的队列。
  2. Fanout exchange(扇型交换机):将消息路由到该交换机绑定的所有队列。
  3. Topic exchange(主题交换机):队列通过路由键绑定到交换机上,交换机根据消息里的路由值,将消息路由给一个或多个绑定队列。
  4. Headers exchange(头交换机):类似主题交换机,但是头交换机使用多个消息属性来代替路由键建立路由规则。当”x-match”为“any”时,消息头的任意一个值被匹配就可以满足条件;当”x-match”设置为“all”的时候,就需要消息头的所有值都匹配成功
  5. 默认存在的交换机:实际上是一个由RabbitMQ预先声明好的名字为空字符串的直连交换机(direct exchange),每个新建队列(queue)都会自动绑定到默认交换机上,绑定的路由键(routing key)名称与队列名称相同。
  6. Dead Letter Exchange(死信交换机):在默认情况,如果消息在投递到交换机时,交换机发现此消息没有匹配的队列,则这个消息将被悄悄丢弃。为了解决这个问题,RabbitMQ中有一种交换机叫死信交换机。当消费者不能处理接收到的消息时,将这个消息重新发布到另外一个队列中,等待重试或者人工干预。这个过程中的exchange和queue就是所谓的”Dead Letter Exchange 和 Queue”
1.2 交换机使用

我们使用如下语句来声明交换机:

channel.exchangeDeclare("logs","fanout");//logs是交换机的名字,fanout是交换机类型

我们可以通过如下命令来列出RabbitMQ Server上的所有交换机:

root@VM-32-73-ubuntu:/home/ubuntu# sudo rabbitmqctl list_exchanges
Listing exchanges ...
	direct
amq.direct	direct
amq.fanout	fanout
amq.headers	headers
amq.match	headers
amq.rabbitmq.log	topic
amq.rabbitmq.trace	topic
amq.topic	topic

其中amp开头的队列都是RabbitMQ自动创建的,用于内部使用,外部无法调用。

在前面的章节中我们是这样发送消息的:

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

其中第一个参数就代表交换机的名字,为空则表示使用默认交换机(1.1 第5点),路由键被设置为队列名。

二、临时队列(Temporary queues)

在前面的例子中,我们声明队列时都指定了队列名(hello、task_queue),但这并不是必须的。如果不指定队列名,则代表使用临时队列:

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

临时队列有如下特性:

  1. 在连接建立时创建,并被分配一个随机名(如amq.gen-JzTY20BRgKO-HjmUJj0wLg)。
  2. 在连接断开后自动销毁。

三、绑定 bindings

交换机和队列都是独立的,要让交换机将消息发送到特定的队列,就需要将它们绑定(binding),我们使用如下语句建立绑定关系:

channel.queueBind(queueName, "logs", "");//queueName代表队列名,logs代表交换机的名字,最后一个参数为路由键(routing key)

我们可以用如下命令查看绑定关系:

root@VM-32-73-ubuntu:/home/ubuntu# rabbitmqctl list_bindings
Listing bindings ...
	exchange	hello	queue	hello	[]
	exchange	task_queue	queue	task_queue	[]

四、整体代码

EmitLog.java 生产者

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 + "'");
    }
  }
}

ReceiveLogs.java 消费者

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 -> { });
  }
}

总结

参考:
https://blog.youkuaiyun.com/hry2015/article/details/79118804

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值