爲什麼需要主題交換機?
在上一篇的學習中,我們改進了日誌系統。我們使用直連交換機代替了扇形交換機,從只能盲目的接收廣播信息改進爲有選擇的接收日誌。
儘管直連交換機能夠改善我們的系統。但是也有缺點 – 沒辦法基於多個標準執行路由操作。
在我們的日誌系統中,我們不只是希望訂閱基於嚴重程度的日誌,同時還希望訂閱基於發送來源的日誌。
如果這樣的話,將會給予我們非常大的靈活性,我們既可以監聽來源於“cron”的嚴重程度爲 critical errors 的日誌,也可以監聽來源於“kern‘的所有的日誌、
爲了實現它,我們接下來學習如何使用另一種更加複雜的交換機 – 主題交換機
主題交換機
發送到主題交換機的消息不可以攜帶隨意什麼樣子的路由鍵,他的路由鍵必須是一個有 . 分隔開的詞語列表。這些單詞隨便是什麼都可以,但是最好跟攜帶它們的消息有關係的詞彙。詞語的個數可以隨意,但是不要超過255字節。
綁定鍵也必須擁有同樣的格式,主題交換機背後的邏輯跟直連交換機很相似 – 一個攜帶着特定路由鍵的消息會被主題交換機投遞給綁定鍵與之想匹配的隊列。 但是他的綁定鍵和路由鍵有兩個特殊的應用方式:
- *(星號)用來表示一個單詞。
- #(井號)用來表示任意數量(零個或多個)單詞
主題交換機是很強大的,他可以表現出跟其他交換類型類似的行爲。
當一個隊列的綁定鍵爲 ”#“ 的時候,這個隊列將會無視消息的路由鍵,接收所有的消息。
但 * 星號 和 # 井號 這兩個特殊字符都未在綁定鍵中出現的時候,此時主題交換機就擁有的直連交換機的行爲。
代碼整合
actionEmitLogTopic 代碼
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
* 生產者 發送信息
*/
namespace yii\console\controllers;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
use yii\console\Controller;
use yii\console\Exception;
use yii\console\ExitCode;
use yii\helpers\Console;
use yii\helpers\FileHelper;
use yii\test\FixtureTrait;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
/**
* Manages fixture data loading and unloading.
*
* ```
* #load fixtures from UsersFixture class with default namespace "tests\unit\fixtures"
* yii fixture/load User
*
* #also a short version of this command (generate action is default)
* yii fixture User
*
* #load all fixtures
* yii fixture "*"
*
* #load all fixtures except User
* yii fixture "*, -User"
*
* #load fixtures with different namespace.
* yii fixture/load User --namespace=alias\my\custom\namespace\goes\here
* ```
*
* The `unload` sub-command can be used similarly to unload fixtures.
*
* @author Mark Jebri <[email protected]>
* @since 2.0
*/
class ProducerController extends Controller
{
private $channel;
private $connection;
public function init ()
{
$amqp = yii::$app->params['amqp'];
//建立一個到RabbitMQ服務器的連接
$this->connection = new AMQPStreamConnection($amqp["host"], $amqp["port"], $amqp["user"], $amqp["password"]);
$this->channel = $this->connection->channel();
}
/**
主題交換機
*/
public function actionEmitLogTopic(){
try{
//設置和發送者是一樣的,我們打開一個連接和一個通道,然後聲明我們將要消耗的隊列。請注意,這與發送的隊列中的隊列相匹配。
//建立一個到RabbitMQ服務器的連接
$connection = $this->connection;
$channel = $this->channel;
//建立topic類型交換機
$channel->exchange_declare('topic_logs','topic',false,false,false);
//發送數據
$parms = func_get_args();
//獲取路由鍵
$routing_key = isset($parms[0]) && !empty($parms[0]) ? $parms[0] : 'anonymous.info';
$data = implode(' ', array_slice($parms, 1));
if (empty($data)) $data = "Hello World";
//處理信息
$msg = new AMQPMessage($data);
//發送信息
$channel->basic_publish($msg,'topic_logs',$routing_key);
$channel->close();
$connection->close();
} catch(\Exception $e) {
echo $e->getMessage();
}
}
}
actionReceiveLogsTopic 代碼
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
* 消費者 接收信息
*/
namespace yii\console\controllers;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
use yii\console\Controller;
use yii\console\Exception;
use yii\console\ExitCode;
use yii\helpers\Console;
use yii\helpers\FileHelper;
use yii\test\FixtureTrait;
use common\tools\Pusher;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
/**
* Manages fixture data loading and unloading.
*
* ```
* #load fixtures from UsersFixture class with default namespace "tests\unit\fixtures"
* yii fixture/load User
*
* #also a short version of this command (generate action is default)
* yii fixture User
*
* #load all fixtures
* yii fixture "*"
*
* #load all fixtures except User
* yii fixture "*, -User"
*
* #load fixtures with different namespace.
* yii fixture/load User --namespace=alias\my\custom\namespace\goes\here
* ```
*
* The `unload` sub-command can be used similarly to unload fixtures.
*
* @author Mark Jebri <[email protected]>
* @since 2.0
*/
class ConsumerController extends Controller
{
private $channel;
private $connection;
public function init ()
{
$amqp = yii::$app->params['amqp'];
//建立一個到RabbitMQ服務器的連接
$this->connection = new AMQPStreamConnection($amqp["host"], $amqp["port"], $amqp["user"], $amqp["password"]);
$this->channel = $this->connection->channel();
}
public function actionReceiveLogsTopic(){
try{
//設置和發送者是一樣的,我們打開一個連接和一個通道,然後聲明我們將要消耗的隊列。請注意,這與發送的隊列中的隊列相匹配。
//建立一個到RabbitMQ服務器的連接
$connection = $this->connection;
$channel = $this->channel;
//創建topic類型交換機
$channel->exchange_declare('topic_logs','topic',false,false,false);
//隨機創建信道
list($queue_name, ,) = $channel->queue_declare("", false, false, true, false);
//發送數據
$parms = func_get_args();
//獲取綁定鍵
$severities = array_slice($parms, 0);
//綁定
foreach($severities as $severity) {
$channel->queue_bind($queue_name, 'topic_logs', $severity);
}
//回調函數
$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();
} catch(\Exception $e){
echo $e->getMessage();
}
}
}
執行下邊命令 接收所有日誌:
./yii rabbitmq-consumer/receive-logs-topic "#"
執行下邊命令 接收來自”kern“設備的日誌:
./yii rabbitmq-consumer/receive-logs-topic "kern.*"
執行下邊命令 只接收嚴重程度爲”critical“的日誌:
./yii rabbitmq-consumer/receive-logs-topic "*.critical"
執行下邊命令 建立多個綁定:
./yii rabbitmq-consumer/receive-logs-topic "kern.*" "*.critical" "kern.#"
執行下邊命令 發送路由鍵爲 “kern.critical” 的日誌:
./yii rabbitmq-producer/emit-log-topic "kern.critical" "A critical kernel error"
gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii rabbitmq-producer/emit-log-topic "kern.critical" "A critical kernel error"
gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii rabbitmq-producer/emit-log-topic "kerns.critical" "A critical kernel error"
gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii rabbitmq-producer/emit-log-topic "kerns.criticals" "A critical kernel error"
gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii rabbitmq-producer/emit-log-topic "kern.critical.test" "A critical kernel error"
gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii rabbitmq-producer/emit-log-topic "kern.criticals" "A critical kernel error"
gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii rabbitmq-producer/emit-log-topic "kern.critical.test" "A critical kernel error"
gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii rabbitmq-producer/emit-log-topic "kern.critical.test" "A critical kernel error"
gongzgiyangdeMacBook-Air:yii2advanced gongzhiyang$ ./yii rabbitmq-producer/emit-log-topic "kern.critical" "A critical kernel error"
執行上邊命令試試看效果,多嘗試幾個例子。