topic模式适配「类型+渠道」灵活路由,因支持(单段)和#(多段)通配符匹配,而fanout会广播、direct不支持模糊匹配;需声明topic交换机,routing_key须为点分三段式(如article.news.wechat),binding_key用article..wechat等规则订阅。

为什么用 topic 模式而不是 fanout 或 direct
因为文章发布需要按「类型 + 渠道」灵活路由:比如 article.news.wechat 发给微信服务,article.video.douyin 发给抖音同步服务,而 fanout 会无差别广播,direct 又不支持通配符匹配。只有 topic 允许用 *(单段)和 #(多段)做模式订阅,真正适配「分类+平台」的组合分发逻辑。
注意:交换机必须声明为 topic 类型,且生产者发消息时的 routing_key 必须是点号分隔的字符串,例如 article.blog.juejin;消费者绑定队列时的 binding_key 才能用 article.blog.* 或 article.# 这类规则。
php-amqplib 中声明 topic 交换机和绑定的实操要点
别直接复用默认交换机(amq.direct),必须显式声明自己的 topic 交换机,并确保所有消费者和生产者使用同一个名字。
- 声明交换机:
$channel->exchange_declare('article.topic', 'topic', false, true, false);—— 第二个参数必须是'topic',auto_delete=false(第三个false)避免连接断开后交换机被删 - 每个平台服务单独声明队列并绑定:
$channel->queue_bind($queue_name, 'article.topic', 'article.*.' . $platform);,比如微信服务用article.*.wechat,这样既能收「新闻」也能收「视频」类微信推送 - 务必设置
$channel->queue_declare(..., false, true, false, true)中的durable=true(第二个true),否则 RabbitMQ 重启后队列消失,未消费消息直接丢弃
发布文章时怎么构造合理的 routing_key
routing_key 不是随便拼的字符串,它决定了谁能收到。建议固定为三段式:{domain}.{category}.{platform},比如:
-
article.news.wechat—— 新闻类文章推微信 -
article.video.douyin—— 视频类文章推抖音 -
article.opinion.juejin—— 观点类文章推掘金
这样设计后,新接入小红书只需加一条绑定 article.*.xiaohongshu,不用改任何发布逻辑。千万别用动态 ID 或时间戳塞进 routing_key,那会彻底破坏路由语义,也丧失通配能力。
消费者端如何避免重复消费或漏消费
RabbitMQ 默认是自动确认(auto_ack=true),一旦消费者进程崩溃,正在处理的消息就丢了。必须关掉它,手动确认:
- 消费前设为手动确认:
$channel->basic_qos(null, 1, null); $channel->basic_consume($queue, '', false, false, false, false, $callback);—— 注意第3个参数false是no_ack,即不自动确认 - 在回调函数里,成功处理完再调
$msg->ack();失败时根据情况调$msg->nack(['requeue' => true])让消息重回队列(适合临时错误),或记录日志后ack()(避免死循环) - PHP 进程退出前要确保
$channel和$connection正常关闭,否则 RabbitMQ 会等超时(默认 30 分钟)才释放 unack 消息
真实线上环境里,最容易被忽略的是消费者进程没做心跳保活,被 RabbitMQ 当作僵死连接踢掉,导致大量消息卡在 unack 状态。加个简单的 pcntl_signal(SIGTERM, ...) 捕获退出信号,再配合 $channel->close() 就能大幅降低这类问题。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











