[TOC] #### 1. 前言 --- 前面我们学了两种 Exchange: + Direct:精确匹配,标签一样才投递 + Fanout:全部广播,不看标签 但有些场景,这两种都不太合适,需要按照指定规则来匹配 为什么需要按规则批量匹配 ? 假设你做了一个日志收集系统,日志来自不同的模块和级别: ```plaintext 订单模块 + 错误级别 → order.error 订单模块 + 信息级别 → order.info 用户模块 + 错误级别 → user.error 用户模块 + 信息级别 → user.info 支付模块 + 错误级别 → pay.error 支付模块 + 信息级别 → pay.info ``` 现在你有几个消费方: + 告警系统:只关心所有模块的 error 日志 + 订单监控:只关心订单模块的所有日志 + 全量日志系统:所有日志都要 如果用 Direct Exchange,你得这样绑定: ```plaintext 告警系统绑定:order.error、user.error、pay.error 订单监控绑定:order.error、order.info 全量日志绑定:order.error、order.info、user.error、user.info、pay.error、pay.info ``` 每加一个模块,绑定列表就要手动更新,日志来源多了之后,维护起来很痛苦 你需要的不是「精确匹配」,而是「按规则匹配」,比如: + 只要是 error 结尾的,都发给告警系统 + 只要是 order 开头的,都发给订单监控 + 所有的都发给全量日志系统 Direct 已经不够用了,你可能会想:用 Direct Exchange,每个日志类型单独绑定,不也能实现吗 ? 能,但代价是: + 10 个模块 × 3 个级别 = 30 种组合,手动绑 30 次 + 每加一个模块,所有相关队列都要重新绑定 + 绑定关系越来越复杂,管理界面密密麻麻 问题的本质是:Direct Exchange 只能精确匹配单个值 你没办法跟它说「只要是 error 结尾的都给我」,你需要的是一种能表达「规则」的匹配方式 #### 2. Topic Exchange --- Topic Exchange 允许你用 通配符 来匹配 + Producer 发消息的时候带一个标记,比如 `order.error` + Queue 绑定的时候用通配符,比如 `*.error` + 只要 Producer 的标记符合通配符的规则,消息就会投递 两个通配符: + \*(星号):匹配 一个 词 + \#(井号):匹配 一个或多个 词 举几个例子: | 绑定规则 | 能匹配 | 不能匹配 | | ------------ | ------------ | ------------ | | \*.error | order.error、user.error | order.info、error | | order.\* | order.error、order.info | user.error | | order.\# | order.error、order.info、order.pay.error | user.error | | \# | 所有 | 无 | 代码示例: ```php // 声明 Topic 类型的 Exchange $channel->exchange_declare('log_exchange', 'topic', false, true, false); // 告警队列:匹配所有 error $channel->queue_declare('alert_queue', false, true, false, false); $channel->queue_bind('alert_queue', 'log_exchange', '*.error'); // 订单监控队列:匹配 order 开头的所有日志 $channel->queue_declare('order_monitor_queue', false, true, false, false); $channel->queue_bind('order_monitor_queue', 'log_exchange', 'order.#'); // 全量日志队列:匹配所有 $channel->queue_declare('all_log_queue', false, true, false, false); $channel->queue_bind('all_log_queue', 'log_exchange', '#'); ``` Producer 发消息: ```php $message = new AMQPMessage('订单创建失败'); $channel->basic_publish($message, 'log_exchange', 'order.error'); ``` 这条消息会同时进入(一条消息,三个队列,各取所需): + alert_queue:因为 order.error 匹配 \*.error + order_monitor_queue:因为 order.error 匹配 order.\# + all_log_queue:因为 order.error 匹配 \# 和 Direct、Fanout 的区别: | 对比项 | Direct | Fanout | Topic | | ------------ | ------------ | ------------ | ------------ | | 匹配方式 | 精确匹配 | 全部广播 | 通配符匹配 | | 灵活性 | 低 | 低 | 高 | | 适用场景 | 按类型分流 | 一条消息多人消费 | 按规则批量投递 | Direct 太精确了,Fanout 太粗暴了,Topic 在中间,既灵活又可控 #### 3. 代码示例 --- ```php require_once __DIR__ . '/vendor/autoload.php'; use PhpAmqpLib\Connection\AMQPStreamConnection; use PhpAmqpLib\Message\AMQPMessage; $connection = new AMQPStreamConnection('192.168.3.20', 5672, 'admin', 'admin123'); $channel = $connection->channel(); // 1.声明 Topic 类型的 Exchange $channel->exchange_declare('log_exchange', 'topic', false, true, false); // 2.声明队列,并绑定到 Exchange 上 # 告警队列:匹配所有 error $channel->queue_declare('alert_queue', false, true, false, false); $channel->queue_bind('alert_queue', 'log_exchange', '*.error'); # 订单监控队列:匹配 order 开头的所有日志 $channel->queue_declare('order_monitor_queue', false, true, false, false); $channel->queue_bind('order_monitor_queue', 'log_exchange', 'order.#'); # 全量日志队列:匹配所有 $channel->queue_declare('all_log_queue', false, true, false, false); $channel->queue_bind('all_log_queue', 'log_exchange', '#'); // 3.Producer 发消息,消息发出去之后,三个队列都会收到一份 $message = new AMQPMessage('订单创建失败'); $channel->basic_publish($message, 'log_exchange', 'order.error'); echo "消息已发送\n"; $channel->close(); $connection->close(); ``` #### 4. 本文小结 --- Topic Exchange 的核心规则:用通配符匹配消息标记,符合规则的就投递 两个通配符: + \*:匹配一个词 + \#:匹配一个或多个词 适用场景: + 日志分级收集 + 按模块、按类型批量分发 + 需要灵活路由规则的场景 记住一句话:Topic Exchange 就是通配符匹配,比 Direct 灵活,比 Fanout 精确 下一讲,我们来看最后一种 Exchange:Headers Exchange