RabbitMQ教程——主题

123 阅读4分钟

主题(Topics)

(使用 amqp.node 客户端)

在之前的教程中,我们通过使用direct交换器替换了只能全部广播的fanout交换器来优化了我们的日志系统,并且获得了有选择性地接收日志的可能性。

尽管使用direct交换器优化了我们的系统,但是它还是有限制的——它不能基于多个规则来执行路由。

在我们的日志系统中,我们可能希望不仅仅只是根据严重程度来订阅日志,而且还要根据发出日志的源头来进行订阅。你可能会从syslogunix 工具中知道这个概念,它会根据严重程度(info/warn/crit...)和设施(auth/cron/kern...)来路由到日志。

这将为我们提供很大的灵活性——我们可能希望监听来自cron的严重级别的错误消息,还可以从kern监听到所有的日志消息。

如果要在我们的日志系统中实现这些,我们需要学习一个更加复杂的topic交换器。

主题交换器(Topic Exchange)

发送给给topic交换器的消息不能拥有随意的routing key——它必须是一串单词,并且由点分隔符分割。单词可以是任意的,但是通常它们指定连接到消息的一些功能。一些合法的路由键的例子如下:stock.usd.nysenyse.vmwquick.orange.rabbit。路由键可以由很多个单词组成,只要你喜欢,它的上限是255个字节。

连接键也需要由相同的格式。topic交换器背后的原理和direct交换器是非常相似的——使用一个特定的路由键发送的消息将会被发送到所有的匹配到连接键的队列。但是连接键有两个重要的特殊情况:

  • *(star)可以替代一个单词。
  • #(hash)可以替代0个或多个单词。

在这个案例中是很容易解释的:

img

在上面的例子中,我们准备发送所有描述动物的消息。这些消息将会由三个单词(两个点)组成的路由键发送出去。路由键中的第一单词会描述速度,第二个描述颜色,第三个描述种类:<speed><colour><species>

我们创建了三个连接:Q1和连接键*.orange.*建立了连接,Q2和连接键*.*.rabbitlazy.#建立了连接。

这些连接可以概括为:

  • Q1对所有橙色的动物感兴趣。
  • Q2想要知道关于兔子的一切和懒惰的动物的一切信息。

一条带有quick.orange.rabbit的路由键的消息将会被发送到两个队列中。lazy.orange.elephant的消息同样会走到两个队列中。然而quick.orange.fox消息只会走第一个队列,并且lazy.brown.fox只会走第二个队列。lazy.pink.rabbit将会被发送到Q2一次,即使两个连接都是满足的,quick.brown.fox由于没有匹配到任何连接,所以它将会被丢弃。

如果我们违反了规定并且发送了一条带有4个单词的消息会发生什么呢,就像orange或者quick.orange.male.rabbit?好的,这些消息不会匹配任何连接,并且最终会丢失。

另一种情况,lazy.orange.male.rabbit虽然它由四个单词组成,但是匹配到最后一个连接,所以会被发送到Q2队列中。

Topic交换器

Topic交换器是非常强大的,并且可以模拟其他类型的交换器的行为。

当一个带有#(hash)连接键的队列时——它将会收到所有的消息,并会忽略路由键——就像fanout交换器。

当在连接中不使用*(star)和#(hash)特殊字符,topic exchange的行为就像direct交换器。

把它们合到一起(Putting it all together)

我们准备在我们的日志系统中使用topic交换器。我们将会开始一个工作假定,日志的路由键将会有两个单词:”<facility>.<severity>“。

代码几乎和上一个教程一样。

emit_log_topic.js代码如下:

#!/usr/bin/env node

var amqp = require('amqplib/callback_api');

amqp.connect('amqp://localhost', function(error0, connection) {
  if (error0) {
    throw error0;
  }
  connection.createChannel(function(error1, channel) {
    if (error1) {
      throw error1;
    }
    var exchange = 'topic_logs';
    var args = process.argv.slice(2);
    var key = (args.length > 0) ? args[0] : 'anonymous.info';
    var msg = args.slice(1).join(' ') || 'Hello World!';

    channel.assertExchange(exchange, 'topic', {
      durable: false
    });
    channel.publish(exchange, key, Buffer.from(msg));
    console.log(" [x] Sent %s:'%s'", key, msg);
  });

  setTimeout(function() {
    connection.close();
    process.exit(0)
  }, 500);
});

receive_logs_topic.js代码如下:

#!/usr/bin/env node

var amqp = require('amqplib/callback_api');

var args = process.argv.slice(2);

if (args.length == 0) {
  console.log("Usage: receive_logs_topic.js <facility>.<severity>");
  process.exit(1);
}

amqp.connect('amqp://localhost', function(error0, connection) {
  if (error0) {
    throw error0;
  }
  connection.createChannel(function(error1, channel) {
    if (error1) {
      throw error1;
    }
    var exchange = 'topic_logs';

    channel.assertExchange(exchange, 'topic', {
      durable: false
    });

    channel.assertQueue('', {
      exclusive: true
    }, function(error2, q) {
      if (error2) {
        throw error2;
      }
      console.log(' [*] Waiting for logs. To exit press CTRL+C');

      args.forEach(function(key) {
        channel.bindQueue(q.queue, exchange, key);
      });

      channel.consume(q.queue, function(msg) {
        console.log(" [x] %s:'%s'", msg.fields.routingKey, msg.content.toString());
      }, {
        noAck: true
      });
    });
  });
});

如果想要收到所有的日志:

./receive_logs_topic.js "#"

如果想要收到kern下的所有日志:

./receive_logs_topic.js "kern.*"

或者你想要监听到仅仅只是critical的日志:

./receive_logs_topic.js "*.critical"

你可以创建多个连接:

./receive_logs_topic.js "kern.*" "*.critical"

并且你可以使用路由键kern.critical发出日志消息并输入:

./emit_log_topic.js "kern.critical" "A critical kernel error"

希望你可以在这些项目上玩的开心。注意,代码不会对路由或者连接键做出任何建设,你也可以使用两个或者更多的路由键参数。

全部代码:emit_log_topic.jsreceive_logs_topic.js

下一步,了解在教程6中如何作为一个远程程序,实现消息的一个往返。