为什么需要主题交换机?
在上一篇的学习中,我们改进了日志系统。我们使用直连交换机代替了扇形交换机,从只能盲目的接收广播信息改进为有选择的接收日志。
尽管直连交换机能够改善我们的系统。但是也有缺点 – 没办法基于多个标准执行路由操作。
在我们的日志系统中,我们不只是希望订阅基于严重程度的日志,同时还希望订阅基于发送来源的日志。
如果这样的话,将会给予我们非常大的灵活性,我们既可以监听来源于“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"
执行上边命令试试看效果,多尝试几个例子。