本文基於 官方文件 翻譯
(using php-amqplib)
在之前的教程中,我們建立了一個工作佇列。 工作佇列背後的假設是每個任務只被傳遞給一個工作人員。 在接下來我們將做一些完全不同的事情 - 我們會向多個消費者傳遞資訊。 這種模式被稱為“釋出/訂閱”。
為了說明這種模式,我們將建立一個簡單的日誌系統。 它將包含兩個程式 - 第一個將傳送日誌訊息,第二個將接收並列印它們。
在我們的日誌系統中,接收程式中的每個執行副本都會收到訊息。 這樣我們就可以執行一個接收器將日誌存到磁碟,與此同時執行另一個接收器在螢幕上列印出日誌。
基本上,釋出的日誌訊息將被廣播給所有的接收者。
交換器 (Exchanges)
在前幾部分中,我們知道了如何傳送訊息並從佇列中接收訊息。 現在,是時候在 Rabbit 中引入完整的訊息傳遞模型。
讓我們快速回顧一下前面教程中的內容:
- producer (生產者)是傳送訊息的使用者應用程式。
- queue (佇列)是儲存訊息的緩衝區。
- consumer (消費者)是接收訊息的使用者應用程式。
RabbitMQ 中的訊息傳遞模型的核心思想是生產者永遠不會將任何訊息直接傳送到佇列中。 實際上,生產者通常甚至不知道郵件是否會被傳送到任何佇列中。
相反,生產者只能傳送訊息給交換器 。 交換是一件非常簡單的事情。 一方面,它接收來自生產者的訊息,另一方則推動他們排隊。 交換器必須知道如何處理收到的訊息。 是否應該附加到特定佇列? 它應該附加到許多佇列中嗎? 或者它應該被丟棄。 這些規則由交換型別定義。
以下為幾種可用的交換型別:direct、topic、headers 和 fanout。 我們將關注最後一個 - fanout(扇出)。 讓我們建立一個這種型別的交換器,並將其稱為日誌:
$channel->exchange_declare('logs', 'fanout', false, false, false);
fanout 交換非常簡單。 正如你可能從名字中猜出的那樣,它只是將收到的所有訊息廣播到它所知道的所有佇列中。 這正是我們記錄器所需要的。
列出交換器
您可以執行永遠有用的 rabbitmqctl 要列出伺服器上的交換器:
sudo rabbitmqctl list_exchanges
在這個列表中將會有一些名為 amq.* 的交換器和預設(未命名)交換器。 這些是預設建立的,但目前不太可能需要使用它們。
預設交換器
在本教程的以前部分,我們對交換器一無所知,但仍能夠將訊息傳送到佇列。 這是可能的,因為我們使用了一個預設的交換,我們用空字串(“”)來標識。
回想一下我們之前如何釋出訊息:
$channel->basic_publish($msg, '', 'hello');
這裡我們使用預設或無名交換:使用由 routing_key 指定的名稱(如果存在)將訊息路由到佇列。 路由鍵是 basic_publish 的第三個引數
現在,我們可以釋出到我們的指定交換器:
$channel->exchange_declare('logs', 'fanout', false, false, false);
$channel->basic_publish($msg, 'logs');
臨時佇列
你可能記得之前我們使用的是具有指定名稱的佇列(請記住 hello 和 task_queue ?)。 能夠命名佇列對我們至關重要 - 我們需要將工作人員(workers)指向同一佇列。 當你想在生產者和消費者之間分享佇列時,給佇列一個名字是很重要的。
但是,我們的記錄器並非如此。 我們希望聽到所有日誌訊息,而不僅僅是其中的一部分。 我們也只對目前流動的訊息感興趣,而不是舊訊息。 要解決這個問題,我們需要兩件事。
首先,每當我們連線到 Rabbit ,我們需要一個新的、空的佇列。 要做到這一點,我們可以建立一個隨機名稱的佇列,最好是 - 讓伺服器為我們選擇一個隨機佇列名稱。
其次,一旦我們斷開消費者,佇列應該被自動刪除。
在 php-amqplib 客戶端中,當我們將佇列名稱作為空字串提供時,會建立一個非持久佇列:
list($queue_name, ,) = $channel->queue_declare("");
當方法返回時,$ queue_name 變數包含由 RabbitMQ 生成的隨機佇列名稱。 例如,它可能看起來像amq.gen-JzTY20BRgKO-HjmUJj0wLg。
因為它被宣告為獨佔,所以當宣告它的連線關閉時,佇列將會被刪除。 您可以在佇列指南中瞭解更多關於獨佔標誌和其它佇列屬性的資訊。
繫結
我們已經建立了一個 fanout 交換器和一個佇列。 現在我們需要告訴交換器將訊息傳送到我們的佇列。 交換和佇列之間的關係稱為繫結。
$channel->queue_bind($queue_name, 'logs');
從現在起,日誌交換器會將訊息附加到我們的佇列中。
列出繫結
猜對了!您可以使用它列出現有的繫結:
rabbitmqctl list_bindings
把它放在一起
發出日誌訊息的生產者程式與之前的教程沒有多大區別。 最重要的變化是我們現在想釋出訊息到我們的日誌交換器,而不是無名字的訊息。 這裡是 emit_log.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('logs', 'fanout', false, false, false);
$data = implode(' ', array_slice($argv, 1));
if(empty($data)) $data = "info: Hello World!";
$msg = new AMQPMessage($data);
$channel->basic_publish($msg, 'logs');
echo " [x] Sent ", $data, "\n";
$channel->close();
$connection->close();
?>
如你所見,建立連線後,我們宣告交換器。 這一步是必要的,因為釋出到不存在的交換器是被禁止的。
如果沒有佇列繫結到交換機上,這些訊息將會丟失,但這對我們來說沒問題; 如果沒有消費者正在收聽,我們可以放心地丟棄訊息。
如果沒有佇列繫結到交換器上,這些訊息將會丟失,但這對我們來說沒問題; 如果沒有消費者正在收聽,我們可以放心地丟棄訊息。
receive_logs.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('logs', 'fanout', false, false, false);
list($queue_name, ,) = $channel->queue_declare("", false, false, true, false);
$channel->queue_bind($queue_name, 'logs');
echo ' [*] Waiting for logs. To exit press CTRL+C', "\n";
$callback = function($msg){
echo ' [x] ', $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.php > logs_from_rabbit.log
如果你想在螢幕上看到日誌,開啟一個新的終端並執行:
php receive_logs.php
And of course, to emit logs type:
當然,要發出日誌訊息:
php emit_log.php
使用 rabbitmqctl list_bindings ,你可以驗證程式碼是否真正建立了繫結和佇列。 有兩個 receive_logs.php 程式在執行,你應該看到如下所示的內容:
sudo rabbitmqctl list_bindings
# => Listing bindings ...
# => logs exchange amq.gen-JzTY20BRgKO-HjmUJj0wLg queue []
# => logs exchange amq.gen-vso0PVvyiRIL2WoV3i48Yg queue []
# => ...done.
結果很明瞭:來自交換器的日誌資料轉到兩個帶有伺服器分配名稱的佇列中。 這正是我們的意圖。
要了解如何監聽訊息的一部分,讓我們繼續閱讀教程4