話題
本文基於 官方文件 翻譯
(using php-amqplib)
在之前的教程中,我們改進了日誌記錄系統。 我們沒有使用只有虛擬廣播的 fanout (扇出)交換器,而是使用了直接交換器,並獲得了選擇性接收日誌的可能性。
儘管使用直接交換改進了我們的系統,但它仍然有侷限性 - 它不能根據多個標準進行路由。
在我們的日誌系統中,我們可能不僅需要根據嚴重性來訂閱日誌,還要根據釋出日誌的來源進行訂閱。 您可能從 syslog unix 工具知道這個概念,該工具根據嚴重性(info/warn/crit...)和工具(auth/cron/kern...)來路由日誌。
這會給我們很大的靈活性 - 我們可能想聽取來自 'cron' 的嚴重錯誤,而且還聽取來自 'kern' 的所有日誌。
為了在我們的日誌系統中實現這一點,我們需要了解更復雜的 topicexchange。
話題交換
傳送到話題交換的訊息不能有任意的 routing_key - 它必須是由點分隔的單詞列表。 單詞可以是任何東西,但通常它們指定了與該訊息相關的一些功能。
一些有效的路由鍵例子:“stock.usd.nyse”,“nyse.vmw”,“quick.orange.rabbit”。 只要您願意,路由鍵中可以有多少個字,最多255個位元組。
繫結鍵也必須是相同的形式。 話題交換背後的邏輯類似於直接話題 - 使用特定路由鍵傳送的訊息將被傳遞到與匹配繫結鍵繫結的所有佇列。 但是繫結鍵有兩個重要的特殊情況:
- * (star) 可以替代一個字。
- # (hash) 可以替代零個或更多的單詞。
用一個很簡單例子可以解釋:
在這個例子中,我們將傳送所有描述動物的訊息。 訊息將使用由三個字(兩個點)組成的路由鍵傳送。 路由關鍵字中的第一個單詞將描述速度,第二個顏色和第三個種類:“ .. ”。
我們建立了三個繫結:Q1 繫結了 “ .orange. ” 和 Q2 繫結了 “ ..rabbit ” 和 “ lazy.# ” 。
These bindings can be summarised as:
這些繫結可以概括為:
- Q1 對所有的橙色動物(*.orange.*)都感興趣。
- Q2 希望監聽關於兔子(*.*.rabbit)的一切,以及關於懶惰動物(lazy.#)的一切。
將路由鍵設定為 “quick.orange.rabbit” 或 “lazy.orange.elephant” ,它們的訊息都將傳遞到兩個佇列。 另一方面,“quick.orange.fox” 只會進入第一個佇列,而 “lazy.brown.fox” 只會進入第二個佇列。 “lazy.pink.rabbit” 只會傳遞到第二個佇列一次,即使它匹配了兩個繫結。 “quick.brown.fox” 不匹配任何繫結,因此將被丟棄。
如果我們違反我們的契約併傳送帶有一個或四個單詞的訊息,如 “orange” 或 “quick.orange.male.rabbit” ,會發生什麼情況? 答案是,這些訊息將不匹配任何繫結並且丟失。
另一方面,“lazy.orange.male.rabbit” 即使有四個單詞,也會匹配最後一個繫結,並將傳遞到第二個佇列。
話題交換
話題交換功能強大,可以像其他交流一樣行事。
當使用 “#”(hash) 繫結鍵繫結佇列時,它將接收所有訊息,而不管路由金鑰如何 - 就像在 fanout (扇出)交換中一樣。
當在繫結中沒有使用特殊字元 “*”(星號)和 “#”(hash)時,主題交換將像直接交換一樣。
把它們放在一起
我們將在我們的日誌系統中使用主題交換。 我們首先假設日誌的路由鍵有兩個字:“.” 。
程式碼幾乎與前一個教程中的程式碼相同。
emit_log_topic.php 的程式碼:
<?php
require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->exchange_declare('topic_logs', 'topic', false, false, false);
$routing_key = isset($argv[1]) && !empty($argv[1]) ? $argv[1] : 'anonymous.info';
$data = implode(' ', array_slice($argv, 2));
if(empty($data)) $data = "Hello World!";
$msg = new AMQPMessage($data);
$channel->basic_publish($msg, 'topic_logs', $routing_key);
echo " [x] Sent ",$routing_key,':',$data," \n";
$channel->close();
$connection->close();
?>
receive_logs_topic.php 的程式碼:
<?php
require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->exchange_declare('topic_logs', 'topic', false, false, false);
list($queue_name, ,) = $channel->queue_declare("", false, false, true, false);
$binding_keys = array_slice($argv, 1);
if( empty($binding_keys )) {
file_put_contents('php://stderr', "Usage: $argv[0] [binding_key]\n");
exit(1);
}
foreach($binding_keys as $binding_key) {
$channel->queue_bind($queue_name, 'topic_logs', $binding_key);
}
echo ' [*] Waiting for logs. To exit press CTRL+C', "\n";
$callback = function($msg){
echo ' [x] ',$msg->delivery_info['routing_key'], ':', $msg->body, "\n";
};
$channel->basic_consume($queue_name, '', false, true, false, false, $callback);
while(count($channel->callbacks)) {
$channel->wait();
}
$channel->close();
$connection->close();
?>
接收所有日誌:
php receive_logs_topic.php "#"
只從接收 “kern” 話題日誌:
php receive_logs_topic.php "kern.*"
或者,如果您只想接收關於 “關鍵” 的日誌資訊:
php receive_logs_topic.php "*.critical"
您可以建立多個繫結:
php receive_logs_topic.php "kern.*" "*.critical"
併發布帶有路由鍵 “kern.critical” 型別的日誌:
php emit_log_topic.php "kern.critical" "A critical kernel error"
玩這些程式玩得開心。 請注意,程式碼沒有對路由或繫結鍵作任何假設,您可能需要使用兩個以上的路由鍵引數。
(Full source code for emit_log_topic.php and receive_logs_topic.php)
接下來,在教程6中,瞭解如何將遠端過程呼叫作為遠端過程呼叫