1. 项目背景
推广中台联调第三天,现场出现「三套真相」:
- 开发用 pika 声明了
ex.orders,自己机器上能发。 - 测试在 CI 容器里跑 Java 客户端,报 NOT_FOUND。事后发现 CI 连的是另一套 VHost,Java 封装把
/编码错了。 - 运维只开了 5672 防火墙,办公网访问不了 15672,只能让开发截图「队列是空的」。空是没消费者,还是根本没声明,截图看不出来。
没有语言无关的操作面,验收就被 SDK 绑死:
开发 SDK 声明拓扑 │ (CI 不会用这个 SDK) ▼ 测试「环境不对」 │ (运维不会看 AMQP 抓包) ▼ 运维「端口通了就是好的」周末值班还发生过:营销同学用 UI 把q.promo.sms改成非持久「先试试」,周一订单组 Java 客户端带着 durable=true 再 declare,直接 406,整个发布列车失败。UI 手滑改拓扑,没有 PR、没有审计,这就是「菜单屏当施工图」的现场代价。管理平面必须规定:谁可以改、用什么工具改、怎么验收。
RabbitMQ 管理平面正好是三部门的公共语言:
| 工具 | 典型使用者 | 擅长 |
|---|---|---|
rabbitmqctl | 运维 | 用户、权限、关连接、节点级操作 |
rabbitmq-diagnostics | 运维 / 测试 | 健康、监听、内存、配置是否生效 |
rabbitmq-plugins | 运维 | 插件名单 |
| Management UI | 全员 | 观察连接、堆积、点选声明(不适合自动化) |
HTTP API/api/* | 测试 / GitOps | 可 curl、可进 CI、可导出 definitions |
rabbitmqadminv2 | 运维脚本 | 比原始 curl 更少编码坑 |
本章规定:拓扑验收以 HTTP API 为准;人眼确认用 UI;危险操作(删用户、关节点)用 ctl 且进审计。开发 SDK 只是数据面的一种客户端,不是真相源。
源码上,HTTP 路由集中在rabbit_mgmt_dispatcher.erl,Cowboy 把/api/overview、/api/exchanges/.../publish等编进同一张表——你在 curl 里敲的路径,就是这张表里的字符串。
还有一层组织痛点:推广中台同时服务订单、营销、数据三个事业部,各部都要「看一眼队列深不深」。若人人 administrator,一次误点 Purge 就能让大促积分对账对空。管理平面的用户标签和 AMQP 权限是两套闸门,必须一起设计,不能只开 15672 端口就算对测试友好。
2. 项目设计
小胖已经打开了 15672,正准备用鼠标把交换机点出来。
小胖:这不就是食堂的电子菜单屏吗?点一点菜就出来了,为啥还要 curl?测试同学学几个按钮不香吗?
大师:菜单屏适合值班看「今天窗口堵不堵」。你用鼠标点出来的队列,没有 diff、没有 PR、没有重复执行。大促当天凌晨三点,测试要在干净 VHost 里一键铺拓扑,只能 API 或 definitions 导入。UI 是观察窗,不是施工图。
技术映射:UI 和 HTTP API 走同一套
rabbit_mgmt_wm_*资源,但 CI 只能打 API,不能打按钮。
小白:那rabbitmqctl和 HTTP API 会不会改到不同的元数据?有没有最终一致延迟?用户标签monitoring能不能声明队列?policymaker呢?万一 CI 用了 administrator,会不会误删生产 definitions?另外 vhost 名叫promo还好,名叫/时 URL 怎么写?
大师:单节点上 ctl 和 API 改的是同一套 Khepri 元数据,通常立刻可见;你要担心的不是延迟,是权限模型:administrator 全能;monitoring 只读监控;policymaker 能改 policy 不能当超级管理员乱用;management 能进 UI 看自己权限范围内的对象。CI 必须用最小标签:铺拓扑用单独的promo_ci账号,configure/write/read 限制在promoVHost,不要administrator。VHost/在 URL 里编码为%2F,这是历史上最多的 404 来源——我们默认业务 VHost 用promo,就是为了少踩这个坑。
小胖:那我用 API 发消息,和用 AMQP publish 有啥不一样?测试能不能只测 API 就算测过 MQ?
大师:API 的 publish/get 是管理通道上的短请求,走 HTTP,确认语义、性能、流控和 AMQP 数据面不同。可以用来做拓扑与浅显投递验收,不能替代消费者 Ack、Confirm、prefetch 测试。小胖记住:API 发一条,只证明「路由存在」;不证明「大促 3000 TPS 数据面正常」。
技术映射:
/api/exchanges/{vhost}/{ex}/publish是管理面;basic.publish才是数据面。
小白:definitions 导出导入能当备份吗?健康检查该打/api/aliveness-test还是新的/api/health/checks/*?dispatcher 里我看到 aliveness 还在,也有一堆 health/checks。
大师:definitions 适合拓扑搬迁,不是 WAL,不能当消息备份。aliveness-test 会在目标 VHost真的声明临时队列并收发,有副作用,高频率探针可能留下垃圾或抢锁;4.x 推荐细粒度/api/health/checks/alarms、local-alarms、virtual-hosts等给 K8s 探针。第 29 章再接到 Operator。今天测试把 checks 写进冒烟,把 aliveness 留作偶尔的「深检」。
小胖:闭环实验就一句话:curl 把菜谱写上墙,再夹一筷子尝尝,UI 围观,ctl 查户口。
大师:对。再加一条:用whoami证明 CI 账号不是 administrator。这比任何规范 PDF 都硬。
小白:HTTP API 的 Basic Auth 走明文,办公网中间人能不能偷到promo_ci?publish 接口会不会被当成后门绕过应用鉴权直接灌消息?测试环境暴露 15672 到公网是否可接受?
大师:预发也必须把 15672 绑在内网或跳板,生产叠加 TLS(第 25 章)。管理面 publish就是一条能灌消息的后门,所以 CI 账号可以 write,值班只读账号绝不能 write。应用鉴权在业务服务;Broker 只认 AMQP/HTTP 用户。两套鉴权都要有,缺管理面闸门时,拿到密码等于拿到路由权。
技术映射:15672 的 publish = 持有 write 权限的管理通道,不是「只读监控端口」。
3. 项目实战
3.1 环境准备
节点:第 3 章基线容器rabbit-promo-1,用户promo/promo_dev_2026,VHostpromo(该用户目前是管理员,仅限本章演示)。
工具:curl(Windows 可用 Git Bash 或curl.exe)、浏览器、docker exec。
约定环境变量(Git Bash):
exportMQAPI=http://127.0.0.1:15672/apiexportMQUSER=promo:promo_dev_2026exportVH=promoWindows PowerShell:
$mq="http://127.0.0.1:15672/api"$pair="promo:promo_dev_2026"$vh="promo"3.2 步骤一:CLI 户口与诊断
步骤目标:分清 ctl / diagnostics / plugins,并列出即将用于 CI 的最小权限用户。
dockerexecrabbit-promo-1 rabbitmq-diagnosticspingdockerexecrabbit-promo-1 rabbitmq-diagnostics listenersdockerexecrabbit-promo-1 rabbitmqctl list_usersdockerexecrabbit-promo-1 rabbitmqctl list_vhostsdockerexecrabbit-promo-1 rabbitmqctl list_user_permissions promodockerexecrabbit-promo-1 rabbitmq-plugins list-E创建测试专用账号(不要再给 administrator):
dockerexecrabbit-promo-1 rabbitmqctl add_user promo_ci ci_dev_2026dockerexecrabbit-promo-1 rabbitmqctl set_user_tags promo_ci managementdockerexecrabbit-promo-1 rabbitmqctl set_permissions-ppromo promo_ci".*"".*"".*"只读观察账号:
dockerexecrabbit-promo-1 rabbitmqctl add_user promo_watch watch_dev_2026dockerexecrabbit-promo-1 rabbitmqctl set_user_tags promo_watch monitoringdockerexecrabbit-promo-1 rabbitmqctl set_permissions-ppromo promo_watch"^$""^$"".*"monitoring+ 空 configure/write:只能看,不能声明。下一步用 API 验证拒绝。
坑:set_user_tags覆盖标签列表,不是追加。写两次后者为准。
坑:权限三个正则依次是 configure、write、read。promo_watch的"^$"表示空串才匹配,即不能声明任何对象。
标签对照(讲给安全评审听):
| tag | 能进 UI | 典型能力 |
|---|---|---|
| administrator | 是 | 全 VHost、全管理操作 |
| monitoring | 是 | 看全局统计,不能乱改拓扑 |
| policymaker | 是 | 管 policy |
| management | 是 | 仅自己有权限的对象 |
| 无 tag | 否 | 纯 AMQP 应用账号(生产推荐) |
3.3 步骤二:UI 走查(人眼,不写进 CI)
打开http://localhost:15672,用promo登录。
必看页面:Overview(节点、告警)、Connections、Channels、Exchanges、Queues、Admin → Users / Vhosts。右上角 VHost 切到promo。
用promo_watch再登录一次:尝试 Add queue,应失败或无入口。把截图贴 Wiki「最小权限证据」。
坑:浏览器缓存了旧用户 cookie,表现为「明明改了权限还是管理员」。无痕窗口。
3.4 步骤三:HTTP API 拓扑闭环(本章核心)
步骤目标:声明直连交换机、持久队列、绑定、发布、拉取,全程 curl。
健康与身份:
curl-s-u"$MQUSER"$MQAPI/whoamicurl-s-u"$MQUSER"$MQAPI/overview|rg-o"\"cluster_name\":\"[^\"]+\""curl-s-u"$MQUSER"$MQAPI/health/checks/alarmscurl-s-u"$MQUSER"$MQAPI/health/checks/virtual-hostswhoami期望含"name":"promo"。alarms在无告警时应是 ok 类响应(HTTP 200)。
运行结果示例:
{"name":"promo","tags":["administrator"]} # health/checks/alarms 无告警时 HTTP 200,body 类似: {"status":"ok"}若whoami返回 401,先查用户是否只在/存在而请求打到了别的认证链;若 200 但 tags 为空,该用户进不了部分 UI 菜单,但 AMQP 仍可能可连。
声明交换机与队列(PUT 幂等):
curl-s-u"$MQUSER"-H"content-type: application/json"\-XPUT$MQAPI/exchanges/$VH/ex.promo.direct\-d'{"type":"direct","durable":true,"auto_delete":false,"internal":false,"arguments":{}}'curl-s-u"$MQUSER"-H"content-type: application/json"\-XPUT$MQAPI/queues/$VH/q.promo.sms\-d'{"durable":true,"auto_delete":false,"arguments":{}}'绑定smsrouting key:
curl-s-u"$MQUSER"-H"content-type: application/json"\-XPOST$MQAPI/bindings/$VH/e/ex.promo.direct/q/q.promo.sms\-d'{"routing_key":"sms","arguments":{}}'发布:
curl-s-u"$MQUSER"-H"content-type: application/json"\-XPOST$MQAPI/exchanges/$VH/ex.promo.direct/publish\-d'{ "properties": {"delivery_mode": 2, "content_type": "application/json"}, "routing_key": "sms", "payload": "{\"orderId\":\"P20260828001\",\"type\":\"PAY_OK\"}", "payload_encoding": "string" }'期望 body 含"routed": true。若"routed": false,绑定错了或 VHost 错了——这就是不靠 SDK 的路由验收。
拉取(管理面 get,会真正把消息取出,注意 ack_mode):
curl-s-u"$MQUSER"-H"content-type: application/json"\-XPOST$MQAPI/queues/$VH/q.promo.sms/get\-d'{"count":1,"ackmode":"ack_requeue_false","encoding":"auto","truncate":50000}'期望 payload 里看到P20260828001。ack_requeue_false表示取出并确认,队列应回到 0。
再发一条,用错误 key:
curl-s-u"$MQUSER"-H"content-type: application/json"\-XPOST$MQAPI/exchanges/$VH/ex.promo.direct/publish\-d'{"properties":{},"routing_key":"no-such","payload":"lost?","payload_encoding":"string"}'期望"routed": false。默认交换器之外,无法路由的消息被丢弃(除非 AMQPmandatory,API 这条短发布不提供完整 mandatory 语义)。测试把routed: false写成明确用例,避免开发以为「HTTP 200 就是送到了」。
坑 1:VHost/必须写成%2F:http://127.0.0.1:15672/api/queues/%2F/my-queue
漏编码会 404。
坑 2:名字里的/、中文必须 URL encode。
坑 3:PUT 队列时 JSON 字段名是durable不是Durable。
坑 4:get的ackmode拼写是ackmode小写,值如ack_requeue_true/reject_requeue_false。写错返回 400。
坑 5:PowerShellcurl是Invoke-WebRequest别名,请用curl.exe。
对照 dispatcher 源码,确认路径不是文档写错:
dispatcher() -> [{"/overview", rabbit_mgmt_wm_overview, []}, {"/cluster-name", rabbit_mgmt_wm_cluster_name, []}, ... {"/exchanges/:vhost/:exchange/publish", rabbit_mgmt_wm_exchange_publish, []}, ... {"/queues/:vhost/:queue/get", rabbit_mgmt_wm_queue_get, []},{"/aliveness-test/:vhost", rabbit_mgmt_wm_aliveness_test, []}, {"/health/checks/alarms", rabbit_mgmt_wm_health_check_alarms, []}, {"/health/checks/local-alarms", rabbit_mgmt_wm_health_check_local_alarms, []}, {"/health/checks/virtual-hosts", rabbit_mgmt_wm_health_check_virtual_hosts, []},实际 HTTP 路径会加上/api前缀(build_module_routes里拼接)。
3.5 步骤四:用最小权限账号证明拒绝
# monitoring 不能 PUT 队列curl-s-o/tmp/watch-body-w"%{http_code}"-upromo_watch:watch_dev_2026\-H"content-type: application/json"\-XPUT$MQAPI/queues/$VH/q.should.fail\-d'{"durable":true}'echocat/tmp/watch-body期望 HTTP401 或 403,队列不存在:
curl-s-o/dev/null-w"%{http_code}"-u"$MQUSER"$MQAPI/queues/$VH/q.should.fail应为 404。
promo_ci(management + 三权限.*)应能完成与步骤三相同的 PUT。
3.6 步骤五:definitions 导出(拓扑版本化)
curl-s-u"$MQUSER"$MQAPI/definitions/$VH>promo-vhost-definitions.json rg"ex.promo.direct|q.promo.sms"promo-vhost-definitions.json把该文件纳入 Git(先检查没有密码字段泄露)。导入:
curl-s-u"$MQUSER"-H"content-type: application/json"\-XPOST$MQAPI/definitions/$VH\--data-binary @promo-vhost-definitions.json坑:全集群/api/definitions含用户哈希,不能当公开附件。按 VHost 导出更安全。
坑:导入不会删除「文件里没有但 Broker 上有」的队列,不是 Terraform apply。
3.7 完整代码清单
promo-mq/ch05/smoke.sh:
#!/usr/bin/env bashset-euopipefailAPI=http://127.0.0.1:15672/apiAUTH=promo:promo_dev_2026VH=promocurl-sf-u"$AUTH""$API/whoami">/dev/nullcurl-sf-u"$AUTH""$API/health/checks/alarms">/dev/nullcurl-sf-u"$AUTH"-H"content-type: application/json"\-XPUT"$API/exchanges/$VH/ex.promo.direct"\-d'{"type":"direct","durable":true,"arguments":{}}'curl-sf-u"$AUTH"-H"content-type: application/json"\-XPUT"$API/queues/$VH/q.promo.sms"\-d'{"durable":true,"arguments":{}}'curl-sf-u"$AUTH"-H"content-type: application/json"\-XPOST"$API/bindings/$VH/e/ex.promo.direct/q/q.promo.sms"\-d'{"routing_key":"sms","arguments":{}}'routed=$(curl-sf-u"$AUTH"-H"content-type: application/json"\-XPOST"$API/exchanges/$VH/ex.promo.direct/publish"\-d'{"properties":{"delivery_mode":2},"routing_key":"sms","payload":"hello-promo","payload_encoding":"string"}')echo"$routed"|rg-q'"routed":true'curl-sf-u"$AUTH"-H"content-type: application/json"\-XPOST"$API/queues/$VH/q.promo.sms/get"\-d'{"count":1,"ackmode":"ack_requeue_false","encoding":"auto"}'|rg-q"hello-promo"echo"CH05 smoke OK"promo-mq/ch05/ smoke.sh promo-vhost-definitions.json # 导出产物,脱敏后入库3.8 测试验证
| 编号 | 步骤 | 期望 |
|---|---|---|
| TC-CH05-01 | whoami | 返回当前用户 |
| TC-CH05-02 | smoke.sh | 退出码 0 |
| TC-CH05-03 | 错误 routing_key publish | routed: false |
| TC-CH05-04 | promo_watchPUT 队列 | 4xx |
| TC-CH05-05 | health/checks/alarms | 无告警时 200 |
| TC-CH05-06 | definitions 含ex.promo.direct | 导出可 grep 到 |
PowerShell 等价冒烟(注意curl.exe):
curl.exe-s-u"promo:promo_dev_2026"http://127.0.0.1:15672/api/whoami4. 项目总结
优点与缺点
| 工具 | 优点 | 缺点 |
|---|---|---|
| HTTP API | 语言无关、可进 CI、路径与源码 dispatcher 一致 | 不是数据面;publish/get 语义不完整 |
| Management UI | 直观、适合值班 | 不可审计施工、易手滑 |
| rabbitmqctl | 节点级能力强、可进运维脚本 | 输出文本要解析;要 erlang cookie |
| SDK 声明拓扑 | 与应用一起发布 | 环境漂移、CI 语言绑死 |
管理平面优点:1)三部门共用验收语言。2)definitions 让拓扑可版本化。3)细粒度 health checks 适合编排探针。
缺点:1)administrator 太好用导致滥用。2)/VHost 编码坑。3)API 200 + routed false 被误当成成功。
适用场景
- 环境冒烟、拓扑验收、权限回归。
- GitOps 导入 VHost definitions。
- 无 SDK 的跳板机排障。
不适用:用/publish打满大促流量;用 UI 当配置存储;把 aliveness-test 每秒打一次当存活探针。
注意事项
- 4.x 健康检查优先
health/checks/*,aliveness 有副作用。 rabbitmqadminv1 下载端点在 4.3 已移除,脚本迁 v2 或固定从旧分支取。- 应用生产账号不要打 management 标签,减少攻击面。
- HTTP API 默认无 CSRF token 需求于基础认证场景,但仍应只在内网或 mTLS(第 25 章)。
- 删除队列
DELETE /api/queues/{vhost}/{name}会丢掉堆积消息,测试清理要显式允许。 - 安全边界:15672 与 5672 权限模型不同,监控账号有 management 标签不等于有 AMQP write,反之亦然,评审要两张表一起看。
- 版本兼容:不同 4.x 的 health check 路径略有增减,CI 不要写死「必须返回某个英文句子」,只断言 HTTP 状态与 JSON 字段。
常见踩坑(生产)
- K8s liveness 打 aliveness-test。节点稍慢时探针失败重启,重启又让 aliveness 更慢,形成死亡循环。根因:选了有副作用的深检。处理:liveness 用
diagnostics ping或health/checks/local-alarms。 - CI 用 administrator 导入全量 definitions,把预发用户覆盖成旧哈希。根因:导出文件含 users。处理:按 VHost 导出,剥离 users。
- 监控账号误配 write 权限,看板点击 Purge。根因:标签与 AMQP 权限两套模型没一起审。处理:monitoring + 空 write。
思考题
- 为什么
routed: true仍不能推出「消费者处理成功」?列出至少三条缺口(持久化、Ack、死信)。 - 若 VHost 名包含
/和空格,你如何写一套「永远正确」的 URL 构造函数?对 encodings 做哪些单测?
(题 1 在第 8–10 章展开;题 2 作为测试基础库作业。)
推广计划提示
| 部门 | 本章怎么用 | 协作 |
|---|---|---|
| 测试 | 主责smoke.sh与权限拒绝用例;API 作为环境门禁 | 向开发要一份「期望拓扑」JSON,而不是口头交换机名 |
| 运维 | 主责用户标签、防火墙 15672 仅办公网/跳板、definitions 备份策略 | 提供无 administrator 的 CI 账号 |
| 开发 | 停止在 README 只写 SDK 示例;补充等价 curl | 数据面仍用 AMQP,不要把 HTTP publish 写进订单热路径 |
| 安全 | 审 whoami、标签、definitions 是否含凭据 | 预发定期扫描 administrator 数量 |
基础篇接下来进入消息模型:第 6 章四种 Exchange 与 Binding。本章留下的ex.promo.direct+q.promo.sms会作为 Direct 路由的第一个对照物。
附录 A:完整清单与仓库位置
rabbitmq-server/column/samples/ch05/smoke.sh即正文脚本。Git 钩子可在 CI 对预发执行,不要在生产每分钟跑get(会把消息掏走)。
常用 API 速查(均前缀/api):
| 方法 | 路径 | 用途 |
|---|---|---|
| GET | /whoami | 当前用户 |
| GET | /overview | 节点摘要 |
| PUT | /exchanges/{vhost}/{name} | 声明交换机 |
| PUT | /queues/{vhost}/{name} | 声明队列 |
| POST | /bindings/{vhost}/e/{ex}/q/{q} | 绑定 |
| POST | /exchanges/{vhost}/{ex}/publish | 管理面发布 |
| POST | /queues/{vhost}/{q}/get | 管理面拉取 |
| GET | /definitions/{vhost} | 导出拓扑 |
| GET | /health/checks/alarms | 告警探针 |
附录 B:第 4 章思考题参考答案
题 1:Confirm 序号与重连。
Confirm 序号按Channel从 1 递增,连接重建后通道是新的,旧序号作废。未收到 Confirm 的消息必须靠业务message_id/订单号幂等重发,不能靠「记住上次 delivery-tag」。只恢复 Connection 不重开 Channel、不重开 confirm.select,等于发布可靠性归零。
题 2:内存告警 blocking 时 basic.get?
资源告警主要阻塞发布(connection blocked),消费与 get 通常仍允许,以便排水。但 Channel 进程是否立刻响应还受流控与客户端心跳影响。第 14 章会用压测把 Overview 上的 Alarm 点亮做实验;若 get 也卡住,优先查磁盘告警和连接是否已被 throttle。
延伸阅读与资源
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 实战修炼与源码剖析