
1. 项目背景第 18 章已经声明了q.order.pay.q。领导要看的不是list_queues typequorum这几个字母而是关掉当前 Leader 容器支付发布 Confirm 仍成功消费者换连后仍能 Ack。另一只脚是毒消息第 16 章经典队列没有投递上限邮差模式全靠应用自觉quorum 在 4.x默认delivery_limit20rabbit_quorum_queue里?DEFAULT_DELIVERY_LIMIT打满后以delivery_limit原因死信避免毒丸打满 Raft 日志。推广中台把支付主路径迁到仲裁队列。约束三节点、单机房、消息体小订单 ID JSON不是附件。不在本章解决跨城、超大消息、超长堆积——那些是 quorum 的反模式堆上去会把rabbit_fifo拖慢连累同节点其它 Raft 组。痛点只改 x-queue-type 以为高可用 ↓ 副本数写成 1 或集群只有 2 节点 ↓ 杀「随便一个容器」却没杀到 Leader误判「没事」 ↓ 消费者 nack requeue 死循环20 次后进死信有人当丢消息 ↓ 或把 limit 设 -1「关闭」毒丸留下集群变慢4.0 起官方强烈建议保留投递限制4.3 允许用 Policy 改 limit 而不必删队列。死信策略at-least-once与 overflowdrop-head不兼容时会回退源码有明确 warning。本章实验用 Direct DLX 默认 at-most-once 死信先把通路跑亮。2. 项目设计小胖把董事会投票照片丢出来三个人两票算过。小胖这不就是少数服从多数吗那我两节点是不是永远选不出杀的不是董事长Leader是不是业务无感Confirm 是等董事长签字还是等两个人签字毒消息投 20 次听着像体罚设成 1 不更快进死信大师两节点没有多数派抗一挂挂一台只剩 1/2写不进去。所以支付最少三节点两票。杀 FollowerLeader 仍在业务本应无感短暂转发。杀 Leader 要选举窗口期发布会变慢或超时客户端必须重试且不得把超时当 SENT第 8 章分桶。Confirm 等的是多数派提交不是「我连的那台机器把 TCP 回了」。投递限制是保护 Raft 的保险丝实验室可把x-delivery-limit设成 3 方便演示生产支付常用 3–1020 是默认不是业务最优。设 1 意味着一次崩溃重投就死信滚动发布会误杀。技术映射Raft Leader 当前日志主写多数派 提交delivery-limit 毒丸保险丝DLX reasondelivery_limit。小白x-quorum-initial-group-size事后能改吗成员少于 size 怎么办杀 Leader 后list_queues leader多久变消费者 exclusive 呢consumer_timeout和 classic 那套还一样吗死信rabbit_fifo_dlx会不会再进原队列成环超大消息边界是经验值还是硬限制大师initial-group-size 管声明时拉多少成员后续加减员是运维命令/自动调和不是再 declare 改 size改了可能 inequivalent。节点不够就凑不够副本可用性按实际成员算。选举通常秒级实验室用循环list_queues name leader看变化不要用睡 1 秒赌。quorum 不支持 exclusive 消费者那套 classic 语义竞争消费多消费者可以。4.3 后 classic 不再按老 consumer_timeout 评估quorum 仍有消费者超时与投递限制两条线超时未 Ack 会重投并计入 limit。DLX 必须指向别的交换机/队列环检测仍重要。超大消息没有「一条禁止 1MB」的单一硬开关但 Raft 日志放大是真实成本支付 JSON 保持很小。小胖实验确认 3 成员 → 发 Confirm → 停 Leader 容器 → 再 Confirm → 拉起节点 → 毒消息 limit3 进 fail。别把 limit 关成 -1。大师停容器前用 ctl 看清当前 leader 是哪台对号入座docker stop。停错 Follower 只能当对照实验。拉起后看 members 恢复 3。技术映射可用性 活着的副本是否构成多数不是「Docker 还剩几台」的直觉。小白Policy 改 delivery-limit 会不会 406与队列 arguments 冲突谁小谁赢杀节点时未 Confirm 的发布怎么处理大师4.3 允许 Policy 改 limit 不必重声明合并时两边都非负则取更小更严有负数关闭则走max那套特殊规则——生产不要用 -1。未 Confirm 必须当失败重试成功以 Confirm 为准。mandatory 对 quorum 仍建议开路由失败不要进 Raft。小胖把第 16 章发布器的 rk 改成pay.ok.q消费者改读q.order.pay.q。检查单加「杀 Leader」。3. 项目实战3.1 环境准备三节点集群健康。若q.order.pay.q已在第 18 章声明且成员为 3可跳过声明补 DLX。dockerexecrabbit1 rabbitmqctl list_queues-porder nametypeleader members运行结果typequorummembers 三人。若队列不存在跑下面声明。3.2 步骤一声明支付 quorum 死信步骤目标3 副本、limit3、DLX 指向第 16 章ex.retry/fail。# promo-mq/ch19/declare_pay_qq.pyimportpika cpika.BlockingConnection(pika.ConnectionParameters(127.0.0.1,5672,order,pika.PlainCredentials(app_order,ord_dev_2026),client_properties{connection_name:ch19-declare}))chc.channel()ch.exchange_declare(ex.order.direct,direct,durableTrue)ch.exchange_declare(ex.retry,direct,durableTrue)ch.queue_declare(q.pay.fail,durableTrue)# 实验室可用 classic 收死信ch.queue_bind(q.pay.fail,ex.retry,fail)ch.queue_declare(q.order.pay.q,durableTrue,arguments{x-queue-type:quorum,x-quorum-initial-group-size:3,x-delivery-limit:3,x-dead-letter-exchange:ex.retry,x-dead-letter-routing-key:fail,x-queue-leader-locator:balanced,})ch.queue_bind(q.order.pay.q,ex.order.direct,pay.ok.q)print(pay quorum ready)c.close()若队列已存在且 arguments 不同会 406用新名字q.order.pay.q3或删实验室队列禁止删生产。运行结果members三个节点名。坑单节点集群声明 group-size3 会不满员。坑DLX 交换机必须已存在。坑fail 队列用 classic 仅实验室生产失败库也可 quorum。3.3 步骤二Confirm 发布# promo-mq/ch19/publish_pay_q.pyimportjson,time,uuid,argparse,pikafrompika.exceptionsimportNackError,UnroutableError apargparse.ArgumentParser()ap.add_argument(--order-id,defaultP-QQ-1)argsap.parse_args()bodyjson.dumps({orderId:args.order_id})cpika.BlockingConnection(pika.ConnectionParameters(127.0.0.1,5672,order,pika.PlainCredentials(app_order,ord_dev_2026),heartbeat30,blocked_connection_timeout10,client_properties{connection_name:ch19-pub}))chc.channel()ch.confirm_delivery()ch.basic_publish(ex.order.direct,pay.ok.q,body.encode(),propertiespika.BasicProperties(delivery_mode2,content_typeapplication/json,message_idstr(uuid.uuid4()),timestampint(time.time())),mandatoryTrue)print(confirmed,args.order_id)c.close()运行结果打印 confirmedmessages≥1。超时抛错分桶记 timeout不写 outbox SENT。3.4 步骤三杀 Leaderdockerexecrabbit1 rabbitmqctl list_queues-porder name leader# 假设 leader 是 rabbitrabbit2dockerstop rabbit2# 循环至多 30sdockerexecrabbit1 rabbitmqctl list_queues-porder name leader members python publish_pay_q.py --order-id P-QQ-AFTER-KILLdockerstart rabbit2dockerexecrabbit2 rabbitmq-diagnostics-qping运行结果停 Leader 后新 leader 变为 rabbit1 或 rabbit3P-QQ-AFTER-KILLConfirm 成功。Follower 被停时对照leader 字段不变Confirm 仍成功。坑连在被停节点上的客户端必须重连 LB。测试应连仍活着的节点端口如 5672再发布。坑停两台只剩一台多数派丢失Confirm 失败——这是预期用来给领导看「为什么是三台」。坑docker stop比kill -9温和实验室够用生产用 drain 再下线第 17 章。3.5 步骤四毒消息打满 delivery-limit# promo-mq/ch19/poison.pyimportpika,os cpika.BlockingConnection(pika.ConnectionParameters(127.0.0.1,5672,order,pika.PlainCredentials(app_order,ord_dev_2026),heartbeat30,client_properties{connection_name:ch19-poison}))chc.channel()ch.basic_qos(prefetch_count1)defon_msg(ch,method,props,body):print(poison redelivered,method.redelivered,tag,method.delivery_tag)ch.basic_nack(method.delivery_tag,requeueTrue)ch.basic_consume(q.order.pay.q,on_msg,auto_ackFalse)print(nacking forever)ch.start_consuming()先python publish_pay_q.py --order-id P-POISON再跑 poison。观察dockerexecrabbit1 rabbitmqctl list_queues-porder name messagesdockerexecrabbit1 rabbitmqctl list_queues-porder name messages--offline||true# fail 队列dockerexecrabbit1 rabbitmqctl list_queues-porder name messages运行结果数次 nack 后q.order.pay.q深度回落q.pay.fail≥1。HTTP Get fail 消息看 headers 含x-deathreason 为delivery_limit。坑默认 20 次现场太慢所以声明写成 3。坑nack requeuefalse直接拒收走的是 rejected 死信不是 limit演示要讲清差别。坑关闭 limit-1后本实验会空转CPU 升高禁止在共享实验室做。3.6 步骤五Policy 调整 limit对照dockerexecrabbit1 rabbitmqctl set_policy-porder p-pay-dl\^q\\.order\\.pay\\.q${delivery-limit:2}--apply-to queues--priority204.3 不必删队列。合并取更严。改完再跑一条毒消息次数变少。演示结束clear_policy。运行结果list_policies可见生效定义含 delivery-limit。坑与第 13 章一样Operator Policy 可能更严。坑pattern 写错打到 stream 上无意义或报错。3.7 步骤六源码与边界口述3 分钟声明参数列表见rabbit_quorum_queuecapabilities含x-dead-letter-*、x-delivery-limit、x-quorum-initial-group-size。get_delivery_limit无配置则 20。dead_letter_publish/5调rabbit_dead_letter。rabbit_ra_systems管 Raft 系统进程现场只点名。边界消息很大、堆积很长、单节点、需要 exclusive、需要破坏性之外的回放——换类型或拆系统。3.8 完整代码清单promo-mq/ch19/ declare_pay_qq.py publish_pay_q.py poison.py kill_leader.sh column/samples/ch19/kill_leader.sh解析 leader → 映射到 container 名 → stop → 等新 leader → 调 publish。3.9 测试验证编号名称期望TC-CH19-01members3三人TC-CH19-02Confirm正常发布成功TC-CH19-03杀 Leader新 leader ConfirmTC-CH19-04杀两台Confirm 失败TC-CH19-05毒消息fail 出现 delivery_limitTC-CH19-06杀 Followerleader 不变Confirm 成功值班检查单下线前 drain看 leader 再动手禁止两节点当支付limit 不得 -1Confirm 超时不得当成功第 16 章检查单的重启项改为「杀单节点不丢支付」。杀 Leader 演练必须留下纪要否则第二年会变成「我们演过好像没问题」。模板如下打印进变更窗口附件日期/操作者 集群节点rabbit1/2/3 演练前 leader / members 停止的容器 新 leader 出现耗时 演练中 Confirm 成功的 orderId 演练中超时或失败的 orderId必须重试且不得写 SENT 消费者是否重连 LB 拉起旧 Leader 后 members 是否回到 3 是否误停两台否口算给领导听三副本要两票停一台剩两台仍两票停两台剩一票写失败。两节点从一开始就没有「停一台还剩多数」。不要用「我们有镜像」解释 4.x quorum镜像是经典队列过时故事。实验室poison.py跑完必须停进程否则会把后续支付测例全部打死信。清理用新的 orderId不要 Purge 支付队列当日常手段。Policy 演示结束要clear_policy避免下次声明与生效 limit 对不上值班以为代码写的是 3 实际是 2。4. 项目总结优点与缺点形态优点缺点3 副本 quorum 支付抗单节点毒丸有保险丝要三节点堆积贵classic 组网简单宿主死亡即不可达关 delivery-limit表面上不进死信毒丸打满 Raft优点1多数派提交与 Confirm 对齐。2limit 可 Policy 调。3杀 Leader 可演可测。缺点1选举窗口要客户端重试。2不适合大消息长堆积。3运维要认 leader/members。对比 Kafka ISR都是多数/副本思维但 quorum 仍是工作队列不是日志。适用场景支付、扣库存指令、必须 Ack 的任务。预发杀 Leader 演练。把第 16 章邮差与 Broker 保险丝结合。不适用审计回放第 20 章两节点凑合网关 exclusive。注意事项最少三节点。安全类型不替代 VHost 权限。版本默认 limit20Policy 改 limit 需 4.3。drain 后再杀进程。常见踩坑生产两节点集群挂一台支付全停却已宣传 HA。根因无多数派。处理加第三节点。滚动发布消费者全部短暂失败消息进死信。根因limit 过小且 nack 重投。处理先提高 limit、滚动双发消费者、查 x-death。杀的不是 Leader演练通过真挂 Leader 时客户端超时未重试。根因演练无效。处理脚本按 leader 字段停容器。思考题为什么 Confirm 成功仍要业务幂等选举窗口可能出现哪些「看起来像双发」的路径把 fail 队列也改成 quorum 有何好处死信风暴时对 Raft 有何风险支付路径至此才算「组网有用」。第 20 章用 stream 解决审计回放不要把 quorum 堆积当日志平台。把第 16 章发布器迁过来时只改三处队列名、routing key、连接目标改为负载均衡而不是写死单容器 IP。不要在发布器里探测 Leader——客户端连任一节点Broker 会把写转到 Leader。探测 Leader 会把运维细节泄漏进业务且 Leader 一变你的缓存就是错的。消费者同样连 LBprefetch 仍按第 9 章设小毒消息实验用 1 便于观察 nack。滚动发布消费者时先起新再停旧避免 limit 较小的队列在空窗把重投打进死信。若必须停光消费者先停发布。这些纪律与经典队列相同但失败代价从「单机堆积」变成「死信库 Raft 负载」更要守。测试夹具注意杀 Leader 用例不要和毒消息用例并行否则 fail 队列里分不清是演练残留还是 limit 命中。每个用例独立 orderId 前缀P-QQ-、P-KILL-、P-POISON-。CI 若没有三容器权限把 TC-CH19-03/04 标为夜间实验室任务不要在单节点 GitHub runner 上绿过就声称 HA 已验。单节点上 quorum 只能证明「能声明」证明不了多数派。这是和第 17 章组网绑定的原因没有三台本章实战就是假的。现场若被问「Follower 落后太多怎么办」回答先看 members 与 Raft 状态不要用重启全集群当第一反应重启会拉长选举窗口支付超时会成片。维护窗口仍然先 drain 再停机与第 17 章同一套口令。演练当天指定一人只负责「禁止同时 stop 两台」与第 16 章指定一人禁止down -v是同一类纪律。领导若只肯给两台机器本章结论只有一句支付不能上 quorum继续单机风险清单不要用两副本假装高可用。两台凑合等于没做本章。附录 C第 18 章思考题参考答案题 1Fanout 三触达改 quorum。Confirm 变为多数派提交延迟略增、可靠性升Ack 仍是破坏性竞争死信与 delivery-limit 可用成本约三倍磁盘与 Raft 开销。短信/邮件/站内信若允许「节点挂了该通道延迟到恢复」可暂留 classic 并写风险支付不得留 classic。不该改 quorum 的是「调试用的 exclusive 临时队列」和「要回放的审计」——后者用 stream。三条触达彼此独立改一条不影响 Fanout 其它绑定。题 2Leader 在最忙节点。优先Policyqueue-leader-locatorbalanced打到支付队列不必发版客户端 arguments 与 Policy 冲突时 Policy 可覆盖 locator。连接层把流量打匀仍然重要locator 管的是队列 Leader 放哪不是消费者连哪。若某 AZ 要亲和那是更后的放置标签不是回到client-local当全局默认。延伸阅读与资源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 实战修炼与源码剖析