(翻译)RabbitMQ官网教程(5)Topics

752 阅读4分钟

原文链接:www.rabbitmq.com/tutorials/t…

在上一章中我们对日志系统进行了改进,将fanout类型的广播交换机替换为direct类型的键绑定交换机,这样就可以根据日志级别来选择性的接收日志消息。

尽管使用direct交换机已经有了很大程度的改进,但还有一个问题就是匹配规则太单一无法满足复杂的匹配规则。

有时候我们不光要根据日志级别来订阅消息,还要根据日志的来源来订阅。你或许从unix的syslog工具中了解过相关概念,它是根据日志的级别(info/warn/crit…)以及系统模块(auth/cron/kern...)来进行路由的。

这就让系统变得更加灵活,我们可能只需要监听cron发出的error级别的日志,以及kern发出的所有日志。

为了在日志系统中实现这种需求,我们需要了解更复杂的topic交换机。

Topic交换机

发送到topic交换机的消息的路由键必须满足一定的格式:用点分隔的单词列表。这些词可以使任何东西,不过一般情况下都是一些描述消息特征的词,比如:"stock.usd.nyse", "nyse.vmw", “quick.orange.rabbit”。路由键中词的数量随你定,只要最终的RoutingKey不超过255byte的限制就行。

绑定建的必须和路由键的格式保持一致。其实topic交换机和direct交换机的机制类似,都是将包含特定路由键的消息分发给能够匹配相应绑定建的队列。不过topic对应的绑定建有注意两点:

  • *可以用来替代任意一个词。
  • #可以用来替代多个词。

其实很容易理解:

在上图的示例中,我们发送的消息都是用来描述动物的。这些消息的路由键都是三个词组成(两个分隔点),第一个词描述速度,第二个词描述颜色,最后一个词描述物种:"..”。

创建的两个绑定关系:Q1队列的绑定建是“*.orange.*,Q2队列的绑定建是"*.*.rabbit”和"lazy.#“”。

这些绑定关系的意思就是:

  • Q1只接收橙色动物相关的消息
  • Q2接收和兔子相关的所有消息以及所有速度慢的动物的消息

路由键为"quick.orange.rabbit" 和"lazy.orange.elephant" 的消息都会分发到这两个队列中,而路由键为“quick.orange.fox”的消息只会被分发到Q1队列中,路由键为"lazy.brown.fox" 的消息只会被分发到Q2队列中。路由键为 “lazy.pink.rabbit"的消息只会被分发到Q2队列中虽然匹配到了两个路由键也只会发送一次。路由键为”quick.brown.fox"的消息由于匹配不到任何的绑定建所以会被忽略丢弃。

如果我们不按照既定格式而是用类似”orange”、” quick.orange.male.rabbit”这样一个词或者四个词来定义路由键的话会发生什么?这样的消息由于匹配不到绑定建最终都会被忽略丢弃。

换句话说尽管"lazy.orange.male.rabbit"这个路由键有四个词,它也只能匹配到最后一个绑定建“lazy.#”,对应的消息会被分发到Q2队列中。

Topic交换机备注:

topic交换机功能强大,能够像其他类型的交换机一样工作。

当队列绑定的建是“#”时,无论消息的路由键是什么,这个队列就会接收所有的消息,就像是在使用fanout交换机。

当绑定建中没有*和#时,topic交换机就会像direct交换机一样工作。

代码整合

这里我们要创建一个用topic交换机支撑的日志系统,我们假定所有消息的路由键都是两个词描述的,格式为".”。

这块的代码和上一章中的代码类型。

EmitLogTopic.java代码:

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

public class EmitLogTopic {

  private static final String EXCHANGE_NAME = "topic_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, "topic");

        String routingKey = getRouting(argv);
        String message = getMessage(argv);

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

ReceiveLogsTopic.java代码:

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

public class ReceiveLogsTopic {

  private static final String EXCHANGE_NAME = "topic_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, "topic");
    String queueName = channel.queueDeclare().getQueue();

    if (argv.length < 1) {
        System.err.println("Usage: ReceiveLogsTopic [binding_key]...");
        System.exit(1);
    }

    for (String bindingKey : argv) {
        channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);
    }

    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 '" +
            delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
    };
    channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });
  }
}

编译运行该样例时所需的依赖与第一章一样,Windows中引用环境变量使用%CP%:

编译:

javac -cp $CP ReceiveLogsTopic.java EmitLogTopic.java

接收所有消息:

java -cp $CP ReceiveLogsTopic "#"

接收kern模块的所有消息:

java -cp $CP ReceiveLogsTopic "kern.*"

只接受critical类型的日志消息:

java -cp $CP ReceiveLogsTopic "*.critical"

多重绑定:

java -cp $CP ReceiveLogsTopic "kern.*" "*.critical"

发送路由键为“kern.critical”的日志消息:

java -cp $CP EmitLogTopic "kern.critical" "A critical kernel error"

尽情折腾吧,所有的路由键和绑定建你可以随便去设置,只要符合RabbitMQ的规则就可以了。

本章完整代码可以查看EmitLogTopicReceiveLogTopic

下一章中我们来了解如何用RabbitMQ来执行RPC调用。