尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

第7章:RabbitMQ Queue 声明、持久化与 Classic Queue

第7章:RabbitMQ Queue 声明、持久化与 Classic Queue 1. 项目背景第 6 章把支付消息送进了q.order.pay。预发第一次重启 Broker后测试发现队列还在里面的「未消费支付成功」却没了。事故会立刻变成财务要对账MQ 里空空如也。现场对「持久化」有三种说法全错了一半开发队列勾了 durable消息就一定还在。运维磁盘没满消息就安全。测试重启容器等于重启应用内存队列也可以。queue.declare durabletrue ← 队列定义能否熬过节点重启 basic.publish delivery_mode2 ← 这条消息是否要求落盘 x-queue-typeclassic ← 用哪套存储引擎本章默认经典队列 CQv2三件套缺一重启后就会「定义在、货不在」或「货在、定义丢了绑定全断」。4.3 还加了一刀默认拒绝非持久且非 exclusive 的队列。老教程里的auto_delete临时队列一 declare 就 406CI 全红有人准备在 conf 里打开deprecated_features.permit.transient_nonexcl_queues——第 3 章已禁止预发这么做。本章只打经典队列。仲裁与 Stream 是第 18–20 章的选型不要在支付主路径上「先用 classic 顶着」而不写进风险清单。测试环境还出现过有人用 Management UI「Purge」把对照队列清空再重启然后宣布「durable 无效」。实验纪律必须写进用例重启前禁止 Purge、禁止 Get、禁止 down -v。否则持久化结论全部作废。运维侧则要确认 compose 的 named volume 还在没有把数据目录指到容器可写层。另一类误解是把auto_delete当成「消息 TTL」。最后一个消费者走了队列被删里面未消费的持久消息一起没——这比非持久消息还冤因为定义层先没了。支付队列禁止 auto_delete。2. 项目设计小胖把快递柜拍照发到群里。小胖队列不就是快递柜格子吗格子写上名字东西放进去。为啥还分耐不耐摔、锁不锁门、人走柜是不是拆掉食堂的餐盘架也没这么多开关。大师格子本身会不会被物业连夜拆走是durable。格子里的包裹贴没贴「贵重」要入库是delivery_mode。这趟只给你临时用、人一走格子消失是exclusive。没人订餐了自动收摊是auto-delete。四个开关组合出完全不同的命运混用就会出现「柜子还在隔夜的盒饭没了」。技术映射durable 管元数据Khepri 里的队列声明persistent 管消息体CQv2 storeexclusive 绑 Connection 生命周期。小白4.3 为什么禁 transient 非 exclusiveexclusive 的非持久为什么还允许x-expires和 auto-delete 什么区别x-max-length满了是丢最老的还是拒绝发布x-overflow有哪些值CQv1 还能声明吗经典队列单机挂了消息是不是一定没大师非持久非 exclusive 等于「写在内存里的公共邮箱」节点一抖全员丢信还假装是共享队列事故不可接受所以废弃特性默认关闭。exclusive 本来就随连接死允许非持久是因为 RPC 回调队列那种短命场景。x-expires是队列闲置 TTL没人用就删定义auto-delete 是最后一个消费者取消后删。x-max-length默认drop-head丢最老reject-publish让发布者失败Confirm 下会 nackreject-publish-dlx则走死信。CQv1 在 4.3移除x-queue-version1会失败CQv2 是经典队列唯一存储。经典队列不复制单机磁盘挂了持久化也救不了——支付主路径第 19 章应上 quorum。小胖那我所有队列都 durable所有消息都 persistent开关全开不就完了大师日志审计用 Stream 更合适网关临时 RPC 用 exclusive。全开的代价是磁盘与 Confirm 延迟。本章实验要对比命运而不是宣传「全部 persistent」。技术映射x-queue-typeclassic是单节点低延迟容器不是金融级副本。小白重启实验怎么做才不算「删了 volume」Dockerrestart与down -v完全不同。还有声明参数事后用 Policy 改max-length和 declare 时写 arguments 谁说了算队列名能不能用中文x-queue-type不写时 4.x 默认是什么exclusive 队列的消息需要 persistent 吗大师docker restart rabbit-promo-1保留/var/lib/rabbitmqdown -v是毁数据。测试用例必须写明。Policy 与 declare 参数合并规则第 13 章今天只在 declare 写x-max-length避免两套数字打架。队列名可以是 UTF-8但 HTTP API 要编码中台规范用 ASCII 点分名。4.x 默认队列类型以节点配置default_queue_type为准很多镜像仍是 classic声明时显式写出x-queue-typeclassic免得出发当天被改成 quorum。exclusive 队列随连接死亡persistent 几乎无意义不必画蛇添足。小胖实验三枪重启看消息在不在、非法临时队列要报错、长度 5 的队列灌 8 条看谁没了。再补一枪auto_delete 队列拉起一个消费者再取消看定义是否消失。3. 项目实战3.1 环境准备同一套promo节点。不要down -v。准备pika与 curl。3.2 步骤一声明三组对照队列步骤目标持久队列 持久消息、持久队列 非持久消息、exclusive 队列为重启实验打基线。# promo-mq/ch07/declare_and_publish.pyimportpika credspika.PlainCredentials(promo,promo_dev_2026)paramspika.ConnectionParameters(127.0.0.1,5672,promo,creds,client_properties{connection_name:ch07-declare})connpika.BlockingConnection(params)chconn.channel()args{x-queue-type:classic}ch.queue_declare(q.cq.durable,durableTrue,argumentsargs)ch.queue_declare(q.cq.durable.transient-msg,durableTrue,argumentsargs)# exclusive 队列连接一关就消失仅用于对照不要当支付队列ex_qch.queue_declare(,exclusiveTrue,durableFalse)print(exclusive queue ,ex_q.method.queue)persistentpika.BasicProperties(delivery_mode2)transientpika.BasicProperties(delivery_mode1)ch.basic_publish(,q.cq.durable,bDUR-MSG,propertiespersistent)ch.basic_publish(,q.cq.durable.transient-msg,bTMP-MSG,propertiestransient)ch.basic_publish(,ex_q.method.queue,bEXCL-MSG,propertiestransient)print(published 3 kinds, keep this process if you want exclusive to survive)# exclusive 队列在 conn.close() 后删除——下面关闭前先让测试 listinput(press Enter to close connection (exclusive will vanish)...)conn.close()先不按 Enter另开终端dockerexecrabbit-promo-1 rabbitmqctl list_queues-ppromo name durable messages exclusive运行结果应看到q.cq.durablemessages≥1、durable trueexclusive 那行 exclusive true。回车关闭连接后 exclusive 队列消失。坑默认交换机 routing_key队列名等于第 6 章 Default Direct。坑queue_declare()才是服务端生成 exclusive 名写死名字 exclusive 也可以但多连接会抢。3.3 步骤二4.3 拒绝 transient 非 exclusive步骤目标不打开废弃特性时declare 失败。# promo-mq/ch07/forbid_transient.pyimportpikafrompika.exceptionsimportChannelClosedByBroker connpika.BlockingConnection(pika.ConnectionParameters(127.0.0.1,5672,promo,pika.PlainCredentials(promo,promo_dev_2026)))chconn.channel()try:ch.queue_declare(q.illegal.transient,durableFalse,exclusiveFalse,auto_deleteFalse)print(UNEXPECTED success)exceptChannelClosedByBrokerase:print(expected fail,e.reply_code,e.reply_text[:200])conn.close()运行结果406 或 PRECONDITION_FAILED文案含 deprecated / transient。源码闸门is_queue_args_combination_permitted(Durable, Exclusive) - case not Durable andalso not Exclusive of false - true; true - rabbit_deprecated_features:is_permitted(transient_nonexcl_queues) end.坑有人加auto_deleteTrue想绕过组合仍可能被拒不要靠咒语改 durable 或 exclusive。坑预发打开 permit 会让老测试变绿、生产升级变炸弹第 3 章基线。CQv1 同样拒绝ch.queue_declare(q.cqv1,durableTrue,arguments{x-queue-version:1})# 4.3 失败3.4 步骤三重启看消息命运步骤目标只docker restart对比两条消息。确保步骤一的两条消息还在不要消费。然后dockerrestart rabbit-promo-1# 等 pingdockerexecrabbit-promo-1 rabbitmq-diagnostics-qpingdockerexecrabbit-promo-1 rabbitmqctl list_queues-ppromo name messages运行结果期望队列重启后消息q.cq.durablepersistent 消息仍在q.cq.durable.transient-msgdelivery_mode1通常没了exclusive定义已随连接消失坑镜像若把数据目录写在容器可写层且没 volumerestart 也可能丢第 1 章 compose 必须挂 volume。坑持久化 ≠ Confirm 已刷盘。本章只验证「重启后还在」的常见路径掉电级保证看第 8、19 章。坑Management UI「Get message」会把消息取出重启前别用手点。CQv2 把消息追加到 segmentindex 记偏移经典队列进程挂了由监督者拉起再从磁盘恢复%% When a message needs to be written to disk, it is appended to %% its corresponding segment file. An offset is returned ... %% Messages are not reference counted, and are not shared between queues.3.5 步骤四x-max-length溢出步骤目标上限 5灌 8 条默认丢掉最老的再对比reject-publish。# promo-mq/ch07/overflow.pyimportpikafrompika.exceptionsimportUnroutableError,NackErrordefpublish_n(name,n,overflowNone):connpika.BlockingConnection(pika.ConnectionParameters(127.0.0.1,5672,promo,pika.PlainCredentials(promo,promo_dev_2026)))chconn.channel()args{x-queue-type:classic,x-max-length:5}ifoverflow:args[x-overflow]overflow ch.queue_declare(name,durableTrue,argumentsargs)ch.queue_purge(name)ch.confirm_delivery()ok,fail0,0foriinrange(n):try:ch.basic_publish(,name,fm{i}.encode(),propertiespika.BasicProperties(delivery_mode2),mandatoryTrue)ok1except(UnroutableError,NackError)ase:fail1print(publish fail,i,type(e).__name__)qch.queue_declare(name,durableTrue,passiveTrue)print(name,ready,q.method.message_count,ok,ok,fail,fail)conn.close()publish_n(q.cq.len.drop,8,None)# drop-headpublish_n(q.cq.len.reject,8,reject-publish)运行结果drop-head队列 ready5最早的m0–m2被丢可用 get 看第一条是否m3。reject-publish在 Confirm 下后几条失败队列不超过 5。坑改x-overflow必须删队列重建否则 inequivalent arg 406。坑无 Confirm 时reject-publish对生产者几乎静默大促会「以为发出去了」。坑x-max-length-bytes按字节和条数上限同时存在时先撞上的生效。x-expires演示可选arguments{x-expires: 60000}表示闲置 60s 删队列测试别拿支付队列做。auto_delete 对照建议测试执行声明q.lab.autodel为 durabletrue、auto_deletetrue用一个消费者basic_consume再立刻 cancel观察队列是否从list_queues消失。支付路径禁止这个组合最后一个实例滚动发布时会把队列删掉。运行结果overflow 补充drop-head 时 UI 的 publish 速率仍在涨队列深度钉死在 5看起来很健康。所以监控不能只看深度上限还要看「被丢弃的消息」——经典队列可看drop_unroutable之外的 overflow 指标或自己在应用记 Confirm nack。否则大促丢最老订单无人知晓。3.6 完整代码清单column/samples/ch07/ declare_and_publish.py forbid_transient.py overflow.py3.7 测试验证编号步骤期望TC-CH07-01非法 transient 队列Channel 406TC-CH07-02restart 后 durablepersistent消息仍在TC-CH07-03restart 后 durabletransient msg消息无TC-CH07-04max-length5 灌 8 drop-headready5TC-CH07-05x-queue-version1声明失败curl-s-upromo:promo_dev_2026\http://127.0.0.1:15672/api/queues/promo/q.cq.durable看durable、typeclassic、messages。值班检查单支付相关队列必须同时满足durable 为真、arguments 里有明确 queue-type、生产者 delivery_mode2、数据目录在 volume 上。缺任何一项重启演练都可能「偶发丢失」事后无法复盘是谁的锅。溢出策略若是默认丢头要在 Wiki 写明「最老订单可能无声消失」并配深度告警资金类应改为拒绝发布并让 Confirm 失败冒泡到 outbox。经典队列的存储层 CQv2 把消息追加进段文件、用 index 记偏移消息不在队列间共享。这意味着同一份 JSON 进两个队列就是两份磁盘。Fanout 放大的不只是内存还有每条队列自己的 store。容量规划时按「副本份数」计而不是按「逻辑消息条数」计。再强调 exclusive 与滚动发布的冲突支付消费者若用 exclusive 队列接活滚动时旧连接断开队列即删新实例看到的是另一张空队列中间的消息直接消失。exclusive 只留给「谁声明谁独享、断连即焚」的 RPC。共享业务邮箱必须是 durable 非 exclusive。4.3 禁 transient 非 exclusive就是防止第三种更糟的「共享却不落盘」。把这三种命运讲给新同事听比让他们背参数表快。声明幂等是另一件要讲清的事queue_declare对已存在且参数等价的队列返回成功这是消费者启动时「确保拓扑」的常规做法。参数不等价则 406滚动发布会有一半实例起不来。所以改 max-length 或 DLX 必须作为明确的变更窗口先扩新队列再切流量或接受短暂删除。禁止在启动脚本里「先删后建」支付队列那会把堆积一键清空。运维变更单里应写「是否删除队列」为单独勾选项默认否。删除等于丢堆积需双人复核。宁可多留一个旧队列也不要在高峰误删。旧队列可改名归档不可直接 Purge。归档队列加 TTL 或后续导出再删。4. 项目总结优点与缺点对象优点缺点Classic durable persistent低延迟、语义简单、适合单机无副本节点挂即风险exclusive 队列RPC 回调干净不能跨连接、不能当业务邮箱非持久消息快、少磁盘重启即丢打开 transient_nonexcl permit兼容老教程4.x 升级定时炸弹优点1声明参数把命运写死可测。2max-length 是防堆积第一闸。3CQv2 去掉 CQv1 的历史坑。缺点1durable 与 persistent 极易混。2溢出默认丢头像无声。3经典队列不能当支付的最终方案。把四开关做成团队口诀定义能否熬夜durable、货是否贵重persistent、是不是私人格子exclusive、人走摊撤不撤auto-delete。评审时逐项打勾比看代码里有没有queue_declare有用。适用场景单机通知、允许短暂丢失的非资金流。RPC 回调 exclusive。用 max-length 保护 Broker。教学与故障演练对照重启。不适用资金、库存扣减的唯一真相源用 quorum无限回放用 Stream把 auto_delete 支付队列当「自动清理」。注意事项4.3 默认禁 transient 非 exclusiveCQv1 移除。改 arguments 先删队列或 inequivalent。安全谁能 declare/delete 队列是 configure 权限。容器必须有数据 volume否则 durable 是假的。常见踩坑生产只 durable 队列消息 delivery_mode1发布重启丢支付通知。根因两层持久化只做了一层。max-length 默认 drop-head客服找不到最早那单。根因没选 reject-publish 告警。CI 打开 permit 临时队列预发忘记关。根因用废弃特性当兼容层。思考题持久化消息在 Confirm 返回后、操作系统掉电前经典队列是否保证已进磁盘还缺哪一层同一经典队列消费者未 Ack 的消息在重启后会出现什么标志题 1 第 8 章题 2 第 9 章redelivered。附录 C第 6 章思考题参考答案题 1两绑定命中同一队列。Broker 会去重到该队列一次路由结果按队列去重是常见实现Topic 的route/3注释写明调用方负责去重。不要依赖「绑两次当重试」。业务去重仍靠 message_id。题 2一次发布进 DirectTopic。可选交换机到交换机绑定Alternate/Exchange-to-exchange、Fanout 前置再由各队列转、应用双发、Shovel。e2e 绑定运维成本高双发要幂等。第 21 章跨集群再用 Shovel。延伸阅读与资源SQLAlchemy 2.0从入门到进阶的实战之旅Dify 从入门到进阶LLM 应用平台实战修炼Java 工程师进阶从 JVM 生产排障到OpenJDK原理NumPy 从入门到生产落地全链路实战指南科学计算/向量化Redis 8 实战精讲从 CRUD 到源码构建高可用缓存系统Redis 实战修炼与原理进阶Python 3实战精进从脚本到高并发订单引擎python入门Rquests从菜鸟脚本到企业级SDK的网络实战圣经Milvus向量数据库实战修炼从 0 到 1精通向量检索与生产落地MongoDB 实战进阶与内核修炼后端工程师的 AI 转型第一课Ollama 与私有化大模型实战10倍开发者的 Dify 魔法书从零构建全栈 AI 应用后端工程师转型AI第一课-Ollama 与私有化大模型实战大型语言模型(LLM) vLLM 高性能推理落地实战Agent开发之LlamaIndex 实战修炼与源码进阶大语言模型Transformers 实战修炼与源码剖析
返回列表