RabbitMQ(六) - 主题(topic)

主题(topic)

在上一个教程中我们改善了我们的日志系统。我们使用direct类型的exchange,可以选择性的接收日志消息,不是像fanout类型的exchange那样,单纯的将消息广播到所有的消费者。

即使使用direct类型的exchange改善了我们的系统,但是它还是有缺陷的 —— 它不能基于多条件进行路由。

在我们的日志系统中,我们可能不仅仅想根据严重性进行订阅,也希望能基于发送日志的来源进行订阅。这个概念来自于unix的工具syslog,根据日志的严重性程度(info/warn/crit...)和设备(auth/cron/kern...)进行路由。

这样能给我们很大的灵活性 - 我们可能只想监听来自cron的致命错误日志和来自kern的所有日志。

为了实现我们的日志系统,我们需要学习更复杂的topic类型的exchange。

主题交换机(topic exchange)

消息发送到topic类型的exchange不可能是任意的routing_key - 它必须是一串单词,以.号隔开。这些单词可以是任意的,但是通常它们指定一些特性跟消息关联起来。一些有效的routing key 例子:stock.usd.nysenyse.vmwquick.orange.rabbit。只要你开心,routing key 可以是很多个单词,最多255个字节。

binding key 必须也是相同的形式。topic exchange背后的逻辑和direct是类似的 —— 一个携带特定routing key的消息将被交付到所有绑定了binding key的相匹配的队列。然而 binding key 有两个重要的特殊情况:

*可以匹配一个完整的单词。
#可以匹配一个或多个单词。

下图可以很容易的解释:

RabbitMQ(六) - 主题(topic)_第1张图片
rabbitmq-topic

在上图中,我们将发送关于描述动物的消息。这个消息的routing key包含三个单词(两个.)。routing key中的第一个单词描述速度,第二个单词描述颜色,第三个描述物种:..

我们创建了三个绑定:Q1绑定的binding key为*.orange.*,Q2的就是*.*.rabbitlazy.#.

这些绑定可以总结为:

  • Q1对橙色的动物感兴趣
  • Q2想知道兔子的一切和懒惰的动物的一切

下面是随着消息的routing key不同被交付到队列也不同的例子:

  • quick.orange.rabbit将交付给所有的队列
  • lazy.orange.elephant也将交付到两个队列
  • quick.orange.fox将只交付给Q1
  • lazy.brown.fox交付到Q2
  • lazy.pink.rabbit匹配了Q2中的两个条件,但是只交付一次
  • quick.brown.fox没有匹配的绑定,将被丢弃

如果我们打破我们的约定,发送一个routing key为一个词或者四个词的消息将会发生什么?例如:orangequick.orange.male,rabbit。这些消息都不匹配,都将被丢弃。

但是也有例外,lazy.orange.male.rabbit虽然有四个词,但是它和最后一个绑定匹配,将被交付到第二个队列。

Topic exchange

topic exchange 是非常强大的,可以实现其他类型的exchange。

当一个队列绑定了#的binding key - 它将接收所有的消息,不用管routing key - 类似于fanout exchange。

*#都没有用在绑定中,topic exchange相当于direct exchange。

信息汇总

我们将使用topic exchange实现我们的日志系统。我们假设routing key 有两个词:.

代码和上个教程差不多。官网设置了五种binding key,分别如下:

  • #
  • kern.*
  • *.critical
  • kern.**.critical
  • kern.critical

我们就发送五条消息,消息分别对应下面的routing key:

  • kern.critical
  • kern.error
  • cron.critical
  • auth.warn
  • cron.info.rolling

下面是代码实现和运行结果:

EmitLogTopic.java

package com.roachfu.tutorial.rabbitmq.website.topic;

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

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

/**
 * topic exchange 生产者
 */
public class EmitLogTopic {

    private static final String EXCHANGE_NAME = "topic.log";

    private static final String[] routingKeys = {"kern.critical", "kern.error", "cron.critical", "auth.warn", "cron.info.rolling"};

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");

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

ReceiveLogTopicAll.java

package com.roachfu.tutorial.rabbitmq.website.topic;

import com.rabbitmq.client.*;

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

/**
 * 接收所有消息:#
 */
public class ReceiveLogTopicAll {

    private static final String EXCHANGE_NAME = "topic.log";

    public static void main(String[] args) throws IOException, TimeoutException {
        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();
        channel.queueBind(queueName, EXCHANGE_NAME, "#");

        System.out.println(" [*] Waiting for all the logs. . .");

        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println(" [x] Received '" + envelope.getRoutingKey() + "':'" + message + "'");
            }
        };
        channel.basicConsume(queueName, consumer);
    }
}

以下消费者只贴出不同代码

ReceiveLogTopicFromFacility.java

channel.queueBind(queueName, EXCHANGE_NAME, "kern.*");

System.out.println(" [*] Waiting for all the logs from the facility 'kern'. . .");

ReceiveLogTopicAboutCritical.java

channel.queueBind(queueName, EXCHANGE_NAME, "*.critical");

System.out.println(" [*] Waiting for all the logs about 'critical' logs. . .");

ReceiveLogTopicMultipleBinding.java

channel.queueBind(queueName, EXCHANGE_NAME, "*.critical");

System.out.println(" [*] Waiting for all the logs by multiple bindings. . .");

ReceiveLogTopicFullMatch.java

channel.queueBind(queueName, EXCHANGE_NAME, "kern.critical");

System.out.println(" [*] Waiting for all the logs from the facility 'kern'. . .");

运行结果

我们还是先开启消费者,然后再开启生产者。得到如下结果:

EmitLogTopic

RabbitMQ(六) - 主题(topic)_第2张图片
rabbitmq-topic-producer

ReceiveLogTopicAll

RabbitMQ(六) - 主题(topic)_第3张图片
rabbitmq-topic-all

ReceiveLogTopicFromFacility

RabbitMQ(六) - 主题(topic)_第4张图片
rabbitmq-topic-fromfacility

ReceiveLogTopicAboutCritical

RabbitMQ(六) - 主题(topic)_第5张图片
rabbitmq-topic-aboutcritical

ReceiveLogTopicMultipleBinding

RabbitMQ(六) - 主题(topic)_第6张图片
rabbitmq-topic-multiplebinding

RecevieLogTopicFullMatch

RabbitMQ(六) - 主题(topic)_第7张图片
rabbitmq-topic-fullmatch

你可能感兴趣的:(RabbitMQ(六) - 主题(topic))