2.2 交换机四大类型与路由规则


文档摘要

2.2 交换机四大类型与路由规则 本节摘要:交换机(Exchange)是消息进入 Broker 后的第一站,本身不存储消息,只做路由判决。AMQP 定义了 direct、fanout、topic、headers 四类交换机,判决规则从"精确匹配"到"完全无视路由键"各不相同。本节逐类拆解判读逻辑,重点讲透 topic 的通配符匹配,并复盘一次 topic 误用导致的消息错投事故。 上一节解剖完消息,现在把它送进迷宫的第一个分岔口。交换机像一个分拣台:消息到达时,它依据自身类型判读路由键与绑定关系,把消息复制投往零个、一个或多个队列。注意"复制"二字——一条消息可以同时进多个队列,每个队列各得一份独立副本,互不影响。理解这一点,广播类业务的设计思路就顺了。

2.2 交换机四大类型与路由规则

本节摘要:交换机(Exchange)是消息进入 Broker 后的第一站,本身不存储消息,只做路由判决。AMQP 定义了 direct、fanout、topic、headers 四类交换机,判决规则从"精确匹配"到"完全无视路由键"各不相同。本节逐类拆解判读逻辑,重点讲透 topic 的通配符匹配,并复盘一次 topic 误用导致的消息错投事故。

上一节解剖完消息,现在把它送进迷宫的第一个分岔口。交换机像一个分拣台:消息到达时,它依据自身类型判读路由键与绑定关系,把消息复制投往零个、一个或多个队列。注意"复制"二字——一条消息可以同时进多个队列,每个队列各得一份独立副本,互不影响。理解这一点,广播类业务的设计思路就顺了。

四类分拣台,四种判词

四类交换机的规则差异,一张表说清:

类型 判读依据 典型场景 常见误用
direct 路由键与绑定键全等 任务分派、点对点指令 想做广播却绑了单键
fanout 无视路由键,复制给所有绑定队列 配置刷新广播、事件扇出 队列一多复制风暴,注意下游容量
topic 按单词通配符模糊匹配 业务域事件订阅、日志分级 通配符写反导致漏收
headers 匹配消息 headers 键值对 多维属性路由 性能低于前三类,少用

direct 的规则最朴素:路由键字符串与绑定键字符串完全一致才投递,一字不差。fanout 走另一个极端:路由键直接被忽略,凡绑定到它的队列人人有份。headers 类把判读依据从路由键换到消息属性表,支持 all(全部键值对命中)与 any(任一命中)两种模式,因解析开销较大且 direct 加 headers 组合常能替代,生产中出场率不高。

topic 是四类中最值得细讲的。 它把路由键和绑定键都视为以点号分隔的单词序列,比如 order.created.paid 是三个单词。绑定键支持两个通配符:星号匹配恰好一个单词,井号匹配零个或多个单词。判定规则举例如下:

  • 绑定键 order.*:命中 order.created,不命中 order.created.paid(星号只认一个单词);
  • 绑定键 order.#:命中 order.createdorder.created.paid(井号贪多);
  • 绑定键 #.error:命中 payment.errororder.inventory.error 等一切以 error 收尾的键。

这套规则让 topic 成为业务事件总线的首选:约定"业务域.事件名.状态"三级路由键,各服务按需订阅自己的子树。

完整演练:一条消息的四种走法

背景:同一个交换机名下不可能有不同类型(同名不同类型会报错),所以建两个交换机对比 fanout 与 topic 的判决差异。

操作:先搭 fanout 与 topic 两个交换机、三个队列,再各发一条消息观察去向:

import pika connection = pika.BlockingConnection(pika.ConnectionParameters(host="localhost")) channel = connection.channel() # 三个下游队列:库存、积分、风控 for q in ["inv.q", "points.q", "risk.q"]: channel.queue_declare(queue=q) # fanout:库存、积分、风控都绑定,路由键随意 channel.exchange_declare(exchange="trace.fanout", exchange_type="fanout") for q in ["inv.q", "points.q", "risk.q"]: channel.queue_bind(exchange="trace.fanout", queue=q, routing_key="") # topic:只有库存和风控订阅订单域,积分订阅全部错误事件 channel.exchange_declare(exchange="trace.topic", exchange_type="topic") channel.queue_bind(exchange="trace.topic", queue="inv.q", routing_key="order.#") channel.queue_bind(exchange="trace.topic", queue="risk.q", routing_key="order.*.failed") channel.queue_bind(exchange="trace.topic", queue="points.q", routing_key="#.error") channel.basic_publish(exchange="trace.fanout", routing_key="x", body=b"broadcast") channel.basic_publish(exchange="trace.topic", routing_key="order.created", body=b"e1") channel.basic_publish(exchange="trace.topic", routing_key="order.created.failed", body=b"e2") channel.basic_publish(exchange="trace.topic", routing_key="payment.error", body=b"e3") print("四条消息已投出")

清点各队列的到货情况:

rabbitmqctl list_queues name messages # 预期输出: # inv.q 4 # points.q 1 # risk.q 1

结果与解读:逐条对账。fanout 那条:三个队列各得一份(三个队列各加 1)。topic 的 order.created:命中 inv.q 的 order.#,risk.q 的 order.*.failed 是三个单词而它只有两个,不命中——inv.q 加 1。order.created.failed:inv.q 的 order.# 命中,risk.q 的 order.*.failed 也命中(三个单词逐位对上),inv.q 与 risk.q 各加 1。payment.error:inv、risk 都不订阅,points.q 的 #.error 命中,points.q 加 1。合计 inv.q 得 3 加 fanout 1 共 4,对上输出。

变式一:井号与星号的边界实验。把 inv.q 的绑定改成 order.*,重发 order.created.failed,它会漏收——单星不跨词。变式二:路由失败的现场。向 topic 交换机发一条 refund.done,没有任何绑定覆盖它,消息无声消失,队列数纹丝不动。生产环境的补救手段(mandatory 标志、备用交换机)在 2.6 节集中对比。

交换机也分"死活"与"来路"

两个易被忽略的声明细节。其一,交换机同样有持久化选项:durable=False 的交换机在 Broker 重启后消失,绑在它上面的路由拓扑全部作废,发布方声明语句就会开始报错——所以资源声明必须与持久化策略整体规划,不能队列持久化而交换机短命。其二,还有两个不请自来的内置交换机:默认交换机(空名字,direct 语义,路由键即队列名,第 1 章的 hello_trace 用过它)以及 amq 前缀的系列内置交换机。默认交换机适合原型验证,生产拓扑建议显式命名,让路由关系在代码里可读可查。

💡 关键直觉:交换机的类型决定"怎么比对",绑定决定"跟谁比对"。两者相遇才有判决。排查路由问题时,先看类型再看绑定,顺序别反。

本节要点回顾

  • 交换机不存消息:只做判决与复制,一条消息可同时进多个队列;
  • 四类规则:direct 全等、fanout 无视键、topic 通配符、headers 属性表;
  • topic 通配符:星号一单词、井号零到多单词,是事件总线的路由基石;
  • 同名同类型约束:同名不同类型声明必报错,类型一旦定下不可更改;
  • 默认交换机:空名 direct 语义,方便原型,生产应显式建拓扑。

四类分拣台看完了,但判决的另一半——绑定契约——还没展开。下一节专门讲绑定与路由键如何共同裁定消息去向。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U