RabbitMQ 原文译05--Topics

在之前的系统中,我们改进了我们的日志系统,我们使用direct 交换机代替fanout交换机,可以实现选择性的接受日志。

 虽然使用direct 交换机改进了我们的系统,但是对于多种条件的判断,依然存在问题。如我们不仅仅想要根据日志的级别来订阅日志,同时也希望可以通过发出日志的源(即日志的生产者)来订阅。你也许已经通过unix 的工具syslog知道了这个概念,它同时通过级别(info/warn/crit...)和源(auth/cron/kern...).这种方式给了我们很大的灵活性--我们可以同时监听来自cron的critical 级别的错误消息和来自kern的所有消息。

为了在我们的系统中实现这种功能,我们需要学习更为复杂的交换机类型 --topic

Topic 交换机

发送到topic的交换机不能是任意routing_key,而必须是一系列使用"."。这些单词可以是任意的单子,单通常情况是是跟当前消息有关的一些功能,例如:"stock.usd.nyse", "nyse.vmw", "quick.orange.rabbit".你可以指定任意多个字符,单上线是255字节。

绑定的key必须也是同样个格式,topic交换机背后的原理类似于把带有特定key的消息发送到所有跟这个key相匹配的队列上去,然而对于bindingKey这里有两个特殊的情况:

1:"*"可以代表一个单词(是单词不是字符--即"."分割的单词)

2:"?"可以代表0个或多个单词

例如:

在这个例子中,我们将要发送描述动物的消息,消息的routingkey将会有3部分组成,routingkey的第一个单词描述的是速度,第二个单词描述的是颜色,第三个单词是特殊描述: "<speed>.<colour>.<species>"。

我们创建了三个绑定,Q1使用bingingkey"*.orange.*",Q2使用"*.*.rabbit" 和"lazy.#"

绑定总结如下:

Q1所有的orange动物感兴趣

Q2想要所有的关于rabbits的消息,和所有的敢于layz动物的消息。

一个带有"quick.orange.rabbit"routingkey的消息会两个队列都接收,"lazy.orange.elephant"的消息同业也会被转发到两个队列上。而"quick.orange.fox"的消息仅仅进入Q1,"lazy.brown.fox"的消息只能被Q2接收。"lazy.pink.rabbit"的消息将再次被转发到Q2上,即使它匹配到了队列2的两个bingdingKey。"quick.brown.fox"的消息不会匹配到任何绑定,所有消息将会被忽略。

如果我们打破我们的约定发送带有4个单词的消息将会发生什么,例如"orange" 或者"quick.orange.male.rabbit"?答案是这些routingkey将不会匹配到任何的bingdingkey,因此将会被忽略

但是"lazy.orange.male.rabbit",因为匹配到了"lazy.#"bingdingkey,所有可以被Q2接收,即使其有4个单词。

Topic 交换机:Topic 交换机功能非常强大,可以完成像其他交换机那样工作。

当队列的bingdingkey使用"#" 时,它将会接收所有的消息,不论routingkey是什么--像fanout交换机那样

当特殊的"*"和"#"都没在bindingkey中使用时,它的行为就像direct交换机那样工作。

汇总

我没将要在我们的日志系统中使用"topic"交换机,我们将定我们的日志系统的routingkey有2个单词:"<facility>.<severity>"

代码和之前的案例基本上是一样的。

 EmitLogTopic.cs:

using System;
using System.Linq;
using RabbitMQ.Client;
using System.Text;

class EmitLogTopic
{
    public static void Main(string[] args)
    {
        var factory = new ConnectionFactory() { HostName = "localhost" };
        using(var connection = factory.CreateConnection())
        using(var channel = connection.CreateModel())
        {
            channel.ExchangeDeclare(exchange: "topic_logs",
                                    type: "topic");

            var routingKey = (args.Length > 0) ? args[0] : "anonymous.info";
            var message = (args.Length > 1)
                          ? string.Join(" ", args.Skip( 1 ).ToArray())
                          : "Hello World!";
            var body = Encoding.UTF8.GetBytes(message);
            channel.BasicPublish(exchange: "topic_logs",
                                 routingKey: routingKey,
                                 basicProperties: null,
                                 body: body);
            Console.WriteLine(" [x] Sent '{0}':'{1}'", routingKey, message);
        }
    }
}
View Code

ReceiveLogsTopic.cs:

using System;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;

class ReceiveLogsTopic
{
    public static void Main(string[] args)
    {
        var factory = new ConnectionFactory() { HostName = "localhost" };
        using(var connection = factory.CreateConnection())
        using(var channel = connection.CreateModel())
        {
            channel.ExchangeDeclare(exchange: "topic_logs", type: "topic");
            var queueName = channel.QueueDeclare().QueueName;

            if(args.Length < 1)
            {
                Console.Error.WriteLine("Usage: {0} [binding_key...]",
                                        Environment.GetCommandLineArgs()[0]);
                Console.WriteLine(" Press [enter] to exit.");
                Console.ReadLine();
                Environment.ExitCode = 1;
                return;
            }

            foreach(var bindingKey in args)
            {
                channel.QueueBind(queue: queueName,
                                  exchange: "topic_logs",
                                  routingKey: bindingKey);
            }

            Console.WriteLine(" [*] Waiting for messages. To exit press CTRL+C");

            var consumer = new EventingBasicConsumer(channel);
            consumer.Received += (model, ea) =>
            {
                var body = ea.Body;
                var message = Encoding.UTF8.GetString(body);
                var routingKey = ea.RoutingKey;
                Console.WriteLine(" [x] Received '{0}':'{1}'",
                                  routingKey,
                                  message);
            };
            channel.BasicConsume(queue: queueName,
                                 noAck: true,
                                 consumer: consumer);

            Console.WriteLine(" Press [enter] to exit.");
            Console.ReadLine();
        }
    }
}
View Code

运行下面的案例:

接收所有的日志:

ReceiveLogsTopic.exe "#"

接收所有的"kern"源的日志:

ReceiveLogsTopic.exe "kern.*"

或者仅仅接受"critical"的日志:

ReceiveLogsTopic.exe "*.critical"

你可以创建多个绑定:

ReceiveLogsTopic.exe "kern.*" "*.critical"

发送一个routingkey是 "kern.critical"的消息

EmitLogTopic.exe "kern.critical" "A critical kernel error"

 

posted @ 2016-06-13 17:20  GrayGuo  阅读(201)  评论(0编辑  收藏  举报