RabbitMQ:路由、确认与重投
交换机、绑定与队列
本轮仅静态审阅,以下命令与练习均未执行;预期结果不是本轮运行证据。早期真实服务验证仅为历史记录,不能代替当前环境验证。
资源模型
前置:节点就绪,执行 Python 命令前先完成 项目依赖,示例 orders 使用全新 prefix 或参数兼容的现有拓扑。exchange 与 queue 的区别体现在责任划分。producer 把消息发布到 exchange;exchange 根据类型和 routing key 选择 binding 的目标 queue;consumer 从 queue 领取。exchange 负责路由,不负责长期保存消息。queue 才承载待处理消息,不能把二者当同义词。
本实验有 orders direct exchange、orders.work 队列和 created 绑定键。publish 到 orders,routing key=created 才进入 orders.work。相同 routing key 绑定多个队列时每个队列可以得到一份;多个消费者读同一队列则竞争分配,不是每人一份。
direct、topic 与 fanout
binding(绑定)把 exchange 与 queue 按路由规则连接起来。direct 对绑定键精确匹配;topic 按点分隔词匹配,* 匹配一个词,# 匹配零个或多个词;fanout 忽略路由键广播到绑定队列。默认空名称 exchange 按队列名称作路由键直达现存队列。这份项目只创建 direct,便于将不可路由失败和绑定关系一一对应,不假装实测了所有类型。
声明是幂等的前提是名称、durable、类型和参数一致。已有同名队列使用不同 x-dead-letter-exchange 再声明会 PRECONDITION_FAILED 并关闭 channel;这不是自动更新配置。生产动态死信策略通常优先使用 policy,本项目参数固定以保持实验自足。
不可路由不是消费失败
没有匹配 binding 的发布可能被正常 exchange 接收却没有队列承接。publisher confirm 和 mandatory 应同时使用:confirm 表示 broker 接受责任,mandatory 使不可路由消息返回。Pika 阻塞通道开启确认后会抛 UnroutableError,本程序捕获到顶层返回非零。
python examples/rabbitmq/main.py --prefix orders setup
python examples/rabbitmq/main.py --prefix orders produce --value order-1 --route created
python examples/rabbitmq/main.py --prefix orders produce --value bad-route --route missing第二条预期 confirmed=true,第三条预期 UnroutableError、退出 1。不要仅凭“broker 已确认”推断业务消费者已执行。重试不可路由消息前先检查 exchange、绑定和 routing key,盲重试只会继续失败。
持久资源与消息
durable=true 保存队列/交换机定义,delivery_mode=2 请求消息持久化,两者需要配合。持久队列中的临时消息不能仅靠队列 durable 推导重启保留;发布确认用于知道 broker 何时接受消息责任。即便确认丢失,生产者重发仍可能产生重复,message_id 只是标识,不会让 RabbitMQ 自动去重。
本篇的机制与失败分支可按综合练习书面推演;提示与参考答案分开提供,无需执行示例。
确认、重投与死信
本轮仅静态审阅,以下命令与练习均未执行;预期结果不是本轮运行证据。早期真实服务验证仅为历史记录,不能代替当前环境验证。
两种确认
两种确认的区别在于确认对象和责任。publisher confirm 是 broker 对发布者的确认;consumer ack 是消费者对某次投递的处理确认。两者独立,confirm 不知道消费者业务完成没有,ack 也不是发给原 producer。项目先 confirm_delivery,再 basic_publish(mandatory=True),成功才报告 confirmed=true。
消息 body 是 UTF-8 字符串,message_id 取实验业务值;真实系统应生成稳定唯一业务标识并定义去重。网络在确认返回前断开时,发布结果可能不确定,非零退出不能推断 broker 一定没收到。
manual ack 与重投
执行前按 项目安装依赖并确认 AMQP 就绪。orders.work 必须为空,否则领取的可能是旧消息;重做练习时可把本页所有 orders 换成同一个新 prefix。
未确认断线重投发生在连接断开后。消费者使用 auto_ack=False。本 CLI 先将投递 JSON 输出并 flush,再 basic_ack(delivery_tag) 或 nack;输出错误不执行确认。真实业务应在实际副作用成功后才确认;delivery tag 属于当前 channel,不能跨 channel ack,也不能同一个 tag 确认两遍。项目 --action leave 故意不确认后关闭连接,broker 将未确认消息重新入队;再次取得时 redelivered=true。
python examples/rabbitmq/main.py --prefix orders produce --value retry-me
python examples/rabbitmq/main.py --prefix orders consume --action leave
python examples/rabbitmq/main.py --prefix orders consume --action ack第二条读取并关闭,第三条再次得到 retry-me 并确认。redelivered 是投递提示,不是可靠业务重试次数;仍然需要业务幂等。程序轮询 basic_get 只适合实验与有界领取,不是高吞吐持续订阅模型。
nack、requeue 与 DLX
basic_nack(requeue=True) 把消息交回原队列,可能立刻再次投递,因此无限重入会形成热循环。basic_nack(requeue=False) 拒绝消息;有死信配置时转发到 DLX,否则可能丢弃。本项目 work 配置 x-dead-letter-exchange 指向 orders.dlx,死信路由键 dead,绑定 orders.dead 队列。
python examples/rabbitmq/main.py --prefix orders produce --value poison
python examples/rabbitmq/main.py --prefix orders consume --action requeue
python examples/rabbitmq/main.py --prefix orders consume --action dead
python examples/rabbitmq/main.py --prefix orders consume --dead开始本节前确认前节 retry-me 已 ack,且 orders.dead 为空。最后一条预期从死信队列取得 poison,headers 中 x-death 记录 rejected 原因及队列信息。死信队列也需要消费者审阅和补偿,不是自动修复。classic queue 的死信转发不能概括为零丢失,DLX 目标不可用时有自己的风险;需要更强保证应专门设计 quorum/死信策略。
背压与失败边界
持续 basic_consume 场景中 prefetch_count 限制未确认投递数,帮助控制工作并行度;本项目设置 1 但使用 basic_get,不能借此宣称测试了预取流控或并行公平性。项目以单次领取和明确超时为边界,空队列超过 deadline 抛 TimeoutError,错误端口连接抛异常并退出 1。
真实业务失败应分类:可短时恢复的可有限重投,确定无法处理的进入死信并带原因,重复事件应按稳定键去重。消费者崩溃后重投无法让外部数据库写入与 ack 原子化。
继续阅读
- 完整实践
- 知识专题
- 知识专题