《RabbitMQ Tutorial》譯文 第 5 章 主題


原文來自 RabbitMQ 英文官網教程(5.Topics),其示例代碼采用了 .NET C# 語言。

Markdown

In the previous tutorial we improved our logging system. Instead of using a fanout exchange only capable of dummy broadcasting, we used a direct one, and gained a possibility of selectively receiving the logs.

在之前的教程中,我們改進了日志系統。比起只能夠單一廣播的 fanout 型交換機,我們現在采用了 direct 型,從而獲得了選擇性接收日志的可能性。

Although using the direct exchange improved our system, it still has limitations - it can't do routing based on multiple criteria.

盡管使用 direct 型交換機對系統有所改進,但仍然有其局限性 - 即在多重條件下沒辦法進行路由。

In our logging system we might want to subscribe to not only logs based on severity, but also based on the source which emitted the log. You might know this concept from the syslog unix tool, which routes logs based on both severity (info/warn/crit...) and facility (auth/cron/kern...).

在該日志系統中,我們可能希望不僅僅訂閱基於嚴重性(這一項指標)的日志,也希望能基於產生日志的源頭。你大概會知道這個概念來自於 syslog unix 工具,它可以針對嚴重性(info/warn/crit...)和設備(auth/cron/kern...)進行路由。

That would give us a lot of flexibility - we may want to listen to just critical errors coming from 'cron' but also all logs from 'kern'.

這便給了我們許多靈活性 - 我們可以做到監聽“cron”類型的嚴重錯誤,以及所有來自於“kern”設備的日志。

To implement that in our logging system we need to learn about a more complex topic exchange.

為了在我們的日志系統中實現上述功能,我們需要學習更為復雜的話題(Topic)交換機。

Topic exchange

話題交換機

Messages sent to a topic exchange can't have an arbitrary routing_key - it must be a list of words, delimited by dots. The words can be anything, but usually they specify some features connected to the message. A few valid routing key examples: "stock.usd.nyse", "nyse.vmw", "quick.orange.rabbit". There can be as many words in the routing key as you like, up to the limit of 255 bytes.

發送至 topic 型交換機的消息已不能攜帶任意的 routing_key - 而必須是一個字符串列表,並基於小數點來分隔。

The binding key must also be in the same form. The logic behind the topic exchange is similar to a direct one - a message sent with a particular routing key will be delivered to all the queues that are bound with a matching binding key. However there are two important special cases for binding keys:

  • * (star) can substitute for exactly one word.
  • # (hash) can substitute for zero or more words.

“綁定鍵”也必須是相同的格式。topic 型交換機背后的邏輯與 direct 型交換機相似 - 采用指定 routing key 所發送的消息將會被遞送到與 binding key 相匹配的隊列中,然而針對 binding key 有兩個非常重要且特殊的情形:

  • * (星號)可以替代一個明確的字符串。
  • # (哈希,井號)可以替代 0 至多個字符串。

It's easiest to explain this in an example:

在示例中可以很容易的對其解釋:

Markdown

In this example, we're going to send messages which all describe animals. The messages will be sent with a routing key that consists of three words (two dots). The first word in the routing key will describe speed, second a colour and third a species: " . . ".

在這個示例中,我們打算發送用來描述動物的消息,它是基於三個字符串(包含兩個小數點)組成的 routing key,其第一個字符串將用來描述速度,第二個是顏色,第三個是物種,如下:“<速度>.<顏色>.<物種>”。

We created three bindings: Q1 is bound with binding key ".orange." and Q2 with "..rabbit" and "lazy.#".

我們創建了三個綁定,隊列 Q1 將關聯到綁定鍵“*.orange.*”,隊列 Q2 則關聯到“*.*.rabbit”和“lazy.#”。

These bindings can be summarised as:

  • Q1 is interested in all the orange animals.
  • Q2 wants to hear everything about rabbits, and everything about lazy animals.

這些綁定可以概括為:

  • 隊列 Q1 對所有桔黃色(orange)的動物感興趣。
  • 隊列 Q2 期望監聽到所有 rabbit 后綴,以及所有 lazy 前綴的動物。

A message with a routing key set to "quick.orange.rabbit" will be delivered to both queues. Message "lazy.orange.elephant" also will go to both of them. On the other hand "quick.orange.fox" will only go to the first queue, and "lazy.brown.fox" only to the second. "lazy.pink.rabbit" will be delivered to the second queue only once, even though it matches two bindings. "quick.brown.fox" doesn't match any binding so it will be discarded.

routing key 設置為"quick.orange.rabbit"的消息將會被遞送到兩個隊列。消息"lazy.orange.elephant"同樣也會去往兩個隊列。另一方面,"quick.orange.fox"將只會去往第一個隊列,"lazy.brown.fox"則只會去第二個隊列。"lazy.pink.rabbit",盡管它匹配了兩個綁定,但只會被遞送到第二個隊列一次(而不會是兩次)。"quick.brown.fox"則不匹配任何綁定,所以將會被丟棄。

What happens if we break our contract and send a message with one or four words, like "orange" or "quick.orange.male.rabbit"? Well, these messages won't match any bindings and will be lost.

如果我們打破約定將會發生什么呢?比如發送一個字符串或者四個字符串,類似於“orange”或者“quick.orange.male.rabbit”。

On the other hand "lazy.orange.male.rabbit", even though it has four words, will match the last binding and will be delivered to the second queue.

另一方面像“lazy.orange.male.rabbit”,即使它有四個字符串,但仍然會匹配最后一個綁定(lazy.#)並被遞送到第二個隊列。

Topic exchange
話題交換機

Topic exchange is powerful and can behave like other exchanges.

話題(Topic)交換機很強大,它可以表現得類似其他交換機。

When a queue is bound with "#" (hash) binding key - it will receive all the messages, regardless of the routing key - like in fanout exchange.

當一個隊列采用"#"作為綁定鍵時,它將接收到所有的消息而忽略掉路由鍵,這就像是 fanout 型交換機一樣。

When special characters "*" (star) and "#" (hash) aren't used in bindings, the topic exchange will behave just like a direct one.

當特殊字符"*" (星號) and "#" (哈希,井號)都沒有應用到綁定中時,topic 型交換機將表現得與 direct 型一樣。

Putting it all together

融合一起

We're going to use a topic exchange in our logging system. We'll start off with a working assumption that the routing keys of logs will have two words: " . ".

我們即將在日志系統中使用 topic 型交換機,首先從一個假定的工作前替起步,即擬定日志的路由鍵為兩個字符串:" . "

The code is almost the same as in the previous tutorial.

其代碼與之前教程幾乎相同。

The code for EmitLogTopic.cs:

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);
        }
    }
}

The code for ReceiveLogsTopic.cs:

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,
                                 autoAck: true,
                                 consumer: consumer);

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

Run the following examples:

運行示例:

To receive all the logs:

接收所有的日志:

cd ReceiveLogsTopic
dotnet run "#"

To receive all logs from the facility "kern":

只接收來自“kern”設備的日志:

cd ReceiveLogsTopic
dotnet run "kern.*"

Or if you want to hear only about "critical" logs:

或者你只想關心“critical”級別的日志:

ReceiveLogsTopic.exe "*.critical"

You can create multiple bindings:

你可以創建多重綁定:

cd ReceiveLogsTopic
dotnet run "kern.*" "*.critical"

And to emit a log with a routing key "kern.critical" type:

基於路由鍵“kern.critical”來產生日志,可以輸入:

cd EmitLogTopic
dotnet run "kern.critical" "A critical kernel error"

Have fun playing with these programs. Note that the code doesn't make any assumption about the routing or binding keys, you may want to play with more than two routing key parameters.

程序執行起來還是很令人愉快的,要注意的是,這些代碼並沒有針對路由鍵或者綁定鍵做任何預設 ,你可能會需要用到超出兩個以上的路由鍵參數。

(Full source code for EmitLogTopic.cs and ReceiveLogsTopic.cs)

( EmitLogTopic.csReceiveLogsTopic.cs 完整代碼)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM