- 后端
- 消息队列
- 消息路由
【免费下载链接】rabbitmq-server
Open source RabbitMQ: core server and tier 1 (built-in) plugins
导读
本文面向 RabbitMQ 运维工程师与插件开发者,系统讲解随 RabbitMQ 3.7.0 起引入的新一代 CLI 工具套件(rabbitmqctl、rabbitmq-plugins、rabbitmq-diagnostics、rabbitmq-queues、rabbitmq-streams、rabbitmq-upgrade)的架构设计、构建方式与扩展机制。读完本文,你将掌握:CLI 命令从参数解析、命令发现、校验到执行输出的完整生命周期;如何用 Elixir 或 Erlang 为 RabbitMQ 编写并注册一个自定义 CLI 命令;以及输出格式化、命令作用域(scopes)、别名(aliases)等插件化扩展能力的具体用法。全部内容均以当前仓库deps/rabbitmq_cli内的文档、源码与测试为事实依据。
RabbitMQ CLI 工具:从 3.7.0 开始的新一代命令行套件
README.md 指出,deps/rabbitmq_cli目录承载的是新一代 RabbitMQ CLI 工具——rabbitmqctl及其兄弟命令。这一代工具最早随 RabbitMQ3.7.0发布,与旧版 CLI 相比有本质区别。
在设计上,Team RabbitMQ 为这一代工具确立了明确的目标:
- 可从插件扩展:插件可以向 CLI 中注入自己的命令;
- 支持可插拔的输出格式:尤其强调机器可读格式(JSON、CSV 等);
- 良好的测试覆盖:每个命令都有对应的 ExUnit 测试;
- 与服务器仓库解耦:CLI 不再内嵌在服务器代码中,而是作为独立组件演进;
- 作为评估 Elixir 的低风险载体:整代 CLI 使用 Elixir 语言实现。
版本配套关系
仓库中长期存在的分支与 RabbitMQ 核心仓库保持同步:master分支对应 rabbitmq-server 的master,v3.10.x分支对应 rabbitmq-server 的v3.10.x,以此类推。因此,请务必使用随 RabbitMQ 发行版本一同分发的 CLI 工具版本,不要混用跨版本的 CLI 与服务器。
构建与安装:从源码生成可执行文件
构建依赖
根据 README.md 的 Building 一节,构建本仓库需要:
- Erlang/OTP 23.3 或更高版本;
- Elixir 1.12.0 或更高版本。
CLI 命令依赖rabbitmq-common(本仓库中的 deps/rabbit_common),依赖关系由erlang.mk解析(仓库根目录可见 erlang.mk 与 rabbitmq-components.mk)。
生成独立可执行文件
本仓库最终产出一个名为rabbitmqctl的可执行文件,该文件通过复制或符号链接成不同名称即可充当不同工具:
rabbitmqctlrabbitmq-pluginsrabbitmq-diagnosticsrabbitmq-queuesrabbitmq-streamsrabbitmq-upgrade
根据可执行文件名称的不同,CLI 会加载并暴露不同集合的命令(--help输出也随之变化)。生成可执行文件的命令为:
make这一"单文件多身份"机制的关键在于命令作用域(scope)选择。从源码看,config.ex 中的get_system_option(:script_name, _)会读取 escript 可执行文件的Path.basename,而 command_modules.ex 中的script_scope/1则依据该名称在应用环境中查找对应的 scope 映射:
| 脚本名称 | 对应 scope(应用环境键) |
|---|---|
rabbitmqctl | :ctl |
rabbitmq-plugins | :plugins |
rabbitmq-diagnostics | :diagnostics |
rabbitmq-queues、rabbitmq-streams、rabbitmq-upgrade等工具则由插件(如rabbitmq_stream、rabbitmq_upgrade)在自身的:scopes环境变量中注册扩展,插件作用域之间可以互相覆盖,但不能覆盖核心作用域,使用时应谨慎。
常用工具的使用入口
rabbitmqctl:查看rabbitmqctl help;rabbitmq-plugins:查看rabbitmq-plugins help;rabbitmq-diagnostics:查看rabbitmq-diagnostics help。
架构总览:一条命令的执行生命周期
DESIGN.md 描述了 CLI 的核心架构。每个命令都是独立模块并实现统一的 behaviour;输出由 formatter(格式化器)与 printer(打印机)两阶段处理。CLI 核心由以下几个模块协同完成命令执行:
| 模块 | 职责 |
|---|---|
RabbitMQCtl | 入口点,通用执行逻辑 |
Parser | 命令行参数解析(驱动 Elixir 标准库OptionParser) |
CommandModules | 命令模块的发现与加载 |
Config | 配置统一:合并环境变量与命令行参数 |
Output | 输出格式化编排 |
Helpers | 工具函数 |
参数解析
命令行参数由 parser.ex 使用 Elixir 的OptionParser解析,返回未命名参数列表与命名选项 Map。第一个未命名参数被视为命令名。命名参数分为全局参数与命令特定参数两类。
全局开关定义在Parser.default_switches/0(parser.ex),常用全局参数如下:
| 参数 | 单字母别名 | 类型 | 说明 | 默认值 |
|---|---|---|---|---|
node | n | atom | 目标 broker 节点名 | rabbit@<当前主机名> |
quiet | q | boolean | 为true时不显示 banner | false |
silent | s | boolean | 同上,抑制 banner 与多余输出 | false |
timeout | t | integer | 超时值,单位秒(部分命令使用) | infinity |
vhost | p | string | 操作的虚拟主机 | / |
formatter | — | string | 输出格式化器 | string |
printer | — | string | 输出打印机 | stdio |
dry-run | — | boolean | 仅打印 banner,不真正执行命令 | false |
longnames | l | boolean | 使用长节点名与 broker 通信(仅当 broker 以 longnames 启动时设为true) | false |
help | ? | boolean | 显示帮助 | false |
print_stacktrace | — | boolean | 出错时打印堆栈 | false |
此外还有一批"环境参数",用于指定服务器相关路径与连接凭据:script-name、rabbitmq-home、data-dir(旧名mnesia-dir,为向后兼容保留为别名)、plugins-dir、enabled-plugins-file、aliases-file、erlang-cookie。
例如命令:
rabbitmqctl list_queues --vhost my_vhost -t 10 --formatter=json name pid --quiet解析结果:未命名参数列表为["list_queues", "name", "pid"],命名选项 Map 为%{vhost: "my_vhost", timeout: 10, quiet: true}。
值得注意的细节:布尔选项不带值时一律解析为true;node、script_name、erlang_cookie三个选项会被原子化(atomize)。命令特定开关通过命令模块的switches/0回调补充进解析器,两者合并时若与全局开关类型冲突会直接报错退出。
命令发现与命名约定
解析出命令名后,command_modules.ex 会把命令名转换为 CamelCase,并查找形如RabbitMQ.CLI.<Scope>.Commands.<CommandName>Command的模块。其筛选逻辑(make_module_map/2)要求候选模块同时满足:
- 模块名匹配正则
RabbitMQ.CLI.(.*).Commands; - 模块确实存在(
Code.ensure_loaded?); - 模块声明实现了
RabbitMQ.CLI.CommandBehaviour; - 模块所属 scope 与当前工具 scope 匹配(见下文)。
命令名与模块名的转换由module_to_command/1完成(去掉命名空间、转为 snake_case 并去掉_command后缀),即模块RabbitMQ.CLI.Ctl.Commands.ListQueuesCommand对应命令list_queues。
从仓库源码可以直观看到命令模块的组织方式:rabbitmqctl的命令位于 deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands(如status、list_queues、add_user等百余个命令);rabbitmq-diagnostics的命令位于 deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands(如check_alarms、listeners、memory_breakdown等);插件命令则分属plugins、queues、streams、upgrade等目录。
命令作用域(Scopes)
命令可通过scopes/0回调声明自己隶属于哪些 scope(如[:ctl, :diagnostics])。若未声明,则默认按命名约定推断:模块RabbitMQ.CLI.MyScope.Commands.DoSomethingCommand自动归入my_scope(snake_case)作用域。scope 由脚本名称或--script-name参数决定,因此同一个命令可以同时出现在rabbitmqctl与rabbitmq-diagnostics中——例如status_command.ex就声明了def scopes(), do: [:ctl, :diagnostics],既可用于rabbitmqctl status,也可用于rabbitmq-diagnostics status。
默认值、校验与执行环境校验
找到命令模块后,执行流程进入 rabbitmqctl.ex 中的决策树:
- 调用
merge_defaults/2合并全局默认值与命令特定默认值,得到"有效参数"; - 调用
validate/2校验 CLI 参数本身;返回{:validation_failure, err}时打印 usage 到 stderr 并以非零码退出(典型为 64); - 调用可选的
validate_execution_environment/2校验执行环境——例如目标节点上 RabbitMQ 是否处于预期状态、文件是否存在可读、环境变量是否导出等; - 全部通过后执行
run/2,其返回值交给output/2处理。
注意 rabbitmqctl.ex 中有一个易被忽略的细节:--timeout全局选项在解析后会被从秒换算为毫秒(timeout * 1000)再传入命令。
命令别名(Aliases)
命令别名提供了一种无需开发插件即可扩展 CLI 的能力。别名文件路径可通过环境变量RABBITMQ_CLI_ALIASES_FILE或--aliases-file参数指定,格式为alias = command [options]:
lq = list_queues lq_vhost1 = list_queues -p vhost1 lq_off = list_queues --offline此时:
RABBITMQ_CLI_ALIASES_FILE=/path/to/aliases.conf rabbitmqctl lq等价于执行rabbitmqctl list_queues;lq_off等价于rabbitmqctl list_queues --offline。内置或插件提供的命令优先于别名查找,因此别名不能覆盖已有命令。
别名还支持带变量与位置参数。命令名必须是=之后第一个词,别名中指定的参数会排在命令行传入参数之前。例如passwd_user1 = change_password user1后,可执行rabbitmqctl passwd_user1 new_password。结合eval命令,别名可以实现强大的管理功能——例如删除某 vhost 下所有队列:
delete_vhost_queues = eval '[rabbit_amqqueue:delete(Q, false, false, <<"rabbit-cli">>) || Q <- rabbit_amqqueue:list(_1)]'_1表示第一个位置参数,调用方式为rabbitmqctl delete_vhost_queues vhost1;也可以使用非数字的下划线变量绑定命名参数,如_vhost,此时调用为rabbitmqctl delete_vhost_queues -p vhost1。需要提醒的是,eval命令只接受全局参数作为命名参数,建议优先使用位置参数;编号参数会以 Elixir 字符串(即 Erlang 二进制)形式传入被求值代码,依赖其类型的代码应自行做类型转换。
输出格式化与打印
output/2的返回值在 output.ex 中被 formatter 与 printer 两阶段处理:
- formatter(格式化器):把输出值翻译为字符串序列,实现
RabbitMQ.CLI.FormatterBehaviour(format_output/2与format_stream/2)。内置格式化器见 deps/rabbitmq_cli/lib/rabbitmq/cli/formatters,包括string、json、csv、erlang、table、pretty_table等。以 json.ex 为例,其format_stream/2将流式数据拼接为合法的 JSON 数组输出,并声明machine_readable?, do: true——机器可读格式化器会自动抑制 banner 输出(见 rabbitmqctl.ex)。 - printer(打印机):把格式化后的字符串渲染到目标设备(stdout、文件等),实现
RabbitMQ.CLI.PrinterBehaviour(init/1、finish/1、print_output/2、print_ok/1)。内置实现见 deps/rabbitmq_cli/lib/rabbitmq/cli/printers:StdIO、StdIORaw与File。默认 formatter 为RabbitMQ.CLI.Formatters.String,默认 printer 为RabbitMQ.CLI.Printers.StdIO(config.ex)。
output/2返回值与退出码的对应关系如下:
output/2返回值 | 行为 | 退出码 |
|---|---|---|
:ok | 调用print_ok,不打印内容 | 0 |
{:ok, value} | formatter 格式化后交给 printer 打印 | 0 |
{:stream, enum} | 流式格式化并逐元素打印;元素为{:error, msg}时停止 | 0或非零(出错时) |
{:error, exit_code, strings} | 打印错误到 stderr | 指定码 |
预定义退出码定义在 exit_codes.ex,采用 BSD sysexits 风格约定:0(成功)、64(usage 错误)、65(数据错误)、67(用户不存在)、69(服务不可用,如节点不可达)、70(软件错误)、75(临时失败,如超时)、78(配置错误)。其中{:validation_failure, :not_enough_args}/:too_many_args映射 64,{:bad_argument, _}映射 65,{:badrpc, :timeout}映射 75,{:badrpc, :nodedown}映射 69。
环境配置来源
命令所需的服务器环境信息(代码目录、mnesia/data 目录、插件目录、enabled plugins 文件等)按以下优先级解析(见 config.ex):命令行参数 > 系统环境变量 > 默认值。环境变量与参数名的对应关系:
| 参数名 | 环境变量 |
|---|---|
rabbitmq-home | RABBITMQ_HOME |
data-dir(mnesia-dir) | RABBITMQ_MNESIA_DIR |
plugins-dir | RABBITMQ_PLUGINS_DIR |
enabled-plugins-file | RABBITMQ_ENABLED_PLUGINS_FILE |
longnames | RABBITMQ_USE_LONGNAME |
node | RABBITMQ_NODENAME |
aliases-file | RABBITMQ_CLI_ALIASES_FILE |
erlang-cookie | RABBITMQ_ERLANG_COOKIE |
在正式发行版中,escript 由 shell/cmd 包装脚本调用,该脚本负责加载 broker 环境并导出为环境变量;同时这些变量也用于定位 enabled plugins 文件与插件目录,从而发现插件提供的命令。
无命令调用与退出码
CLI 不带任何参数调用时视为无效调用,会打印全部命令 usage 并返回退出码64(与 curl、grep 的行为一致);带--help或help命令则打印 usage 并返回0;--version会被重写为version命令,--auto-complete会被重写为autocomplete命令(见 rabbitmqctl.ex)。输入不存在的命令名时,解析器还会通过 auto_complete.ex 给出 "Did you mean ..." 的拼写建议。
自定义命令开发:从 Behaviour 到可运行命令
插件化扩展是本代 CLI 的立身之本。开发自定义命令需要满足三个条件(详见 COMMAND_TUTORIAL.md):
- 遵循命名约定:模块名匹配
RabbitMQ.CLI.(.*).Commands.(.*)Command; - 包含在插件应用的模块列表中:即出现在
.app文件的modules字段; - 实现
RabbitMQ.CLI.CommandBehaviourbehaviour。
CommandBehaviour 接口
behaviour 的完整定义见 command_behaviour.ex。必须实现的回调有六个:
| 回调 | 签名 | 职责 |
|---|---|---|
usage/0 | String.t \| [String.t] | 命令用法字符串,展示在命令列表中 |
banner/2 | (list, map) :: String.t | 执行前打印的提示;--quiet时忽略,--dry-run时只打印 banner |
merge_defaults/2 | (list, map) :: {list, map} | 合并默认参数与选项,返回"有效参数" |
validate/2 | (list, map) :: :ok \| {:validation_failure, ...} | 校验 CLI 参数 |
run/2 | (list, map) :: any | 命令主体逻辑,通常包含对 broker 的 RPC 调用 |
output/2 | (any, map) :: :ok \| {:ok, any} \| {:stream, enum} \| {:error, code, [String.t]} | 将run/2返回值转换为输出与退出码 |
可选回调包括:
switches/0:命令特定开关(名称与类型的关键字列表),如[offline: :boolean, time: :integer]会把--offline --time=100解析为%{offline: true, time: 100};aliases/0:开关的单字母别名,如[o: :offline, t: :timeout];formatter/0:默认输出格式化器模块;printer/0:默认输出打印机模块;scopes/0:命令所属作用域列表;usage_additional/0:附加到 usage 之后的补充说明;usage_doc_guides/0:关联的文档指南链接;description/0、help_section/0:帮助文本与分组;validate_execution_environment/2:执行环境校验(文件存在性、RabbitMQ 运行状态等),签名与validate/2相同,未定义时视为:ok;distribution/1:控制 Erlang 分布式通信,可取:cli(默认,使用 rabbitmqctl 生成的节点名)、:none(禁用分布式,适合离线命令)、{:fun, fun}(自定义启动逻辑)。
实战教程:编写一个删除队列的命令
以 COMMAND_TUTORIAL.md 的示例为基础,一步步实现delete_queue命令。
第一步:声明模块与 behaviour
defmodule RabbitMQ.CLI.Ctl.Commands.DeleteQueueCommand do @behaviour RabbitMQ.CLI.CommandBehaviour end此时编译会报出一串未定义 behaviour 函数的警告(usage/0、banner/2、merge_defaults/2、validate/2、run/2、output/2),逐一实现即可。
第二步:声明开关与别名
def switches(), do: [if_empty: :boolean, if_unused: :boolean] def aliases(), do: [e: :if_empty, u: :is_unused]注意vhost不需要在此声明——它是全局开关,所有命令天然可用。switches/0与aliases/0都是可选的:没有短别名可省略aliases/0,没有命名参数可两者都省略。
第三步:实现 banner
def banner([qname], %{vhost: vhost, if_empty: if_empty, if_unused: if_unused}) do if_empty_str = case if_empty do true -> "if queue is empty" false -> "" end if_unused_str = case if_unused do true -> "if queue is unused" false -> "" end "Deleting queue #{qname} on vhost #{vhost} " <> Enum.join([if_empty_str, if_unused_str], " and ") end第四步:默认值与参数校验
def merge_defaults(args, options) do { args, Map.merge(%{if_empty: false, if_unused: false, vhost: "/"}, options) } end def validate([], _options) do {:validation_failure, :not_enough_args} end def validate([_,_|_], _options) do {:validation_failure, :too_many_args} end def validate([""], _options) do {:validation_failure, {:bad_argument, "queue name cannot be empty string."}} end def validate([_], _options) do :ok endmerge_defaults/2返回的有效参数会依次传给validate/2、banner/2、run/2。虽然行为未强制,但一个实用命令至少应有一个validate/2分支返回:ok。
第五步:实现 run/2(远程执行)
def run([qname], %{node: node, vhost: vhost, if_empty: if_empty, if_unused: if_unused}) do ## 由队列名与 vhost 生成资源名 queue_resource = :rabbit_misc.r(vhost, :queue, qname) ## 在 broker 节点上查找队列 case :rabbit_misc.rpc_call(node, :rabbit_amqqueue, :lookup, [queue_resource]) do {:ok, queue} -> ## 删除队列 :rabbit_misc.rpc_call(node, :rabbit_amqqueue, :delete, [queue, if_empty, if_unused]); {:error, _} = error -> error end end关键要点:run/2中可以直接使用rabbit_common的任意函数,但要对远程 broker 节点执行操作,必须走 RPC——可以使用标准 Erlangrpc:call系列,也可以使用rabbit_misc:rpc_call/4(所有标准命令都使用后者,官方推荐)。目标节点名通过全局选项node传入,对所有命令可用。
第六步:实现 output/2
def output({:error, :not_found}, _options) do {:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage, "Queue not found"} end def output({:error, :not_empty}, _options) do {:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage, "Queue is not empty"} end def output({:error, :in_use}, _options) do {:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage, "Queue is in use"} end def output({:ok, queue_length}, _options) do {:ok, "Queue was successfully deleted with #{queue_length} messages"} end ## 其余情况使用默认输出 use RabbitMQ.CLI.DefaultOutputoutput/2返回{:ok, result}表示成功,返回{:error, exit_code, message}表示失败(exit_code必须是整数,message是字符串或字符串列表)。程序失败时以exit_code退出,成功时以0退出。RabbitMQ.CLI.DefaultOutput负责兜底处理常见错误,例如目标节点无法联系或 Erlang cookie 认证失败时的badrpc错误——这正是 rabbitmqctl.ex 中badrpc_error_message_header/2输出的诊断信息(节点不可达、cookie 不匹配、节点未运行等常见原因列表)。
第七步:测试运行
rabbitmqctl delete_queue my_queue --vhost my_vhost把命令模块加入插件、编译、启用插件后即可生效。
仓库中的真实实现对照
教程中的示例在仓库里有完整的真实实现可供对照——delete_queue_command.ex 是rabbitmqctl delete_queue的正式实现,结构完全遵循上述模式,并在此基础上增加了force开关、timeout开关(单字母别名t)、usage_additional/0参数说明、help_section分组与description。它还通过use RabbitMQ.CLI.Core.RequiresRabbitAppRunning引入了执行环境校验——要求目标节点上 rabbit 应用处于运行状态,这正是validate_execution_environment/2机制的典型用法(该宏对应的模块见 deps/rabbitmq_cli/lib/rabbitmq/cli/core/requires_rabbit_app_running.ex)。
另一个值得精读的最小但完整的示例是status命令:status_command.ex 展示了如何为不同 formatter 提供不同输出——默认输出一段带章节标题(Runtime、Plugins、Memory、Free Disk Space、Totals、Listeners 等)的可读文本,--formatter json时输出结构化 Map,--formatter erlang时直接返回原始 term。它声明了scopes() [:ctl, :diagnostics]、默认单位unit: "gb"、超时默认 60 秒,并通过usage_additional/0说明--unit与--formatter的取值。对应测试见 test/ctl/status_command_test.exs,覆盖了参数校验(多余参数报too_many_args)、真实节点上run/2返回pid、不存在节点返回badrpc、banner 内容等场景,是学习命令测试写法的范本。
用 Erlang 实现命令
由于 CLI 用 Elixir 编写,用 Erlang 实现命令时需注意模块名与 behaviour 名都必须加Elixir前缀,且其中含点号,需用单引号转义为合法原子:
-module('Elixir.RabbitMQ.CLI.Ctl.Commands.DeleteQueueCommand'). -behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour').其余回调用 Erlang 语法等价实现(switches/0返回[{if_empty, boolean}, ...],merge_defaults/2使用maps:merge/2,output/2末尾调用'Elixir.RabbitMQ.CLI.DefaultOutput':output(Other, Options, ?MODULE)兜底)。完整 Erlang 示例见 COMMAND_TUTORIAL.md 的 "Full Module Example in Erlang" 一节。
测试与贡献
CLI 具备"良好的测试覆盖"这一设计目标,测试体系采用 Elixir ExUnit(mix test),测试文件与源码目录一一对应,位于 deps/rabbitmq_cli/test,例如ctl子目录对应rabbitmqctl的命令、queues/streams/plugins等子目录对应各工具。贡献指南见 CONTRIBUTING.md。
结语
从架构上看,这一代 RabbitMQ CLI 的核心设计决策——每个命令独立成模块、统一 behaviour 接口、作用域驱动的命令发现、可插拔的 formatter/printer、基于命令名的自动模块映射——共同构成了一个低耦合、易扩展、可测试的命令行框架。无论是运维中需要组合rabbitmqctl、rabbitmq-diagnostics、rabbitmq-queues等工具排查问题,还是通过插件或别名文件向 CLI 注入自定义管理能力,理解本文所述的命令生命周期与行为接口都是最可靠的上手路径。进一步深入可阅读仓库内的 DESIGN.md(架构与全局参数详解)与 COMMAND_TUTORIAL.md(命令开发完整教程)。
- 后端
- 消息队列
- 消息路由
【免费下载链接】rabbitmq-server
Open source RabbitMQ: core server and tier 1 (built-in) plugins
相关推荐
Backstage CLI 概览:模块化构建工具链与自定义命令开发指南
Backstage CLI 概览:模块化构建工具链与自定义命令开发指南 Backstage 的目标是让开发者在项目内外的体验都足够愉悦:创建新 app http
开发者门户后端前端Vapor命令行工具:自定义Command开发和CLI应用构建
Vapor命令行工具:自定义Command开发和CLI应用构建 还在为复杂的Web应用管理而烦恼?Vapor框架提供了强大的命令行工具系统,让你能够轻松创建自定
后端Web框架深入理解 RabbitMQ CLI 命令架构:基于 CommandBehaviour 的插件命令开发实战
深入理解 RabbitMQ CLI 命令架构:基于 CommandBehaviour 的插件命令开发实战 RabbitMQ 自 3.7.0 起,其官方 CLI
后端消息队列消息路由
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考