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

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

我们创建了三个绑定: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 有两个词:<facility>.<severity>

代码和上个教程差不多。官网设置了五种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-producer

ReceiveLogTopicAll

rabbitmq-topic-all

ReceiveLogTopicFromFacility

rabbitmq-topic-fromfacility

ReceiveLogTopicAboutCritical

rabbitmq-topic-aboutcritical

ReceiveLogTopicMultipleBinding

rabbitmq-topic-multiplebinding

RecevieLogTopicFullMatch

rabbitmq-topic-fullmatch
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容

  • 之前几节已经学习过fanout exchange,direct exchange的使用,并用他们构建了一个...
    初级赛亚人阅读 591评论 0 2
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,657评论 18 139
  • 来源 RabbitMQ是用Erlang实现的一个高并发高可靠AMQP消息队列服务器。支持消息的持久化、事务、拥塞控...
    jiangmo阅读 10,361评论 2 34
  • rabbitMQ是一款基于AMQP协议的消息中间件,它能够在应用之间提供可靠的消息传输。在易用性,扩展性,高可用性...
    点融黑帮阅读 3,002评论 3 41
  • rabbitmq RabbitMQ官方入门教程 本文算是实现对入门教程的 java版本翻译吧。本文中演示代码地址 ...
    我不是李小龙阅读 557评论 0 1