STOMP 插件
概述
RabbitMQ 通过核心发行版中自带的插件支持 STOMP 协议。该插件支持 STOMP 1.0 到 1.2 版本,并包含一些扩展和限制。
STOMP 客户端可以与其他协议互操作。管理界面中的所有功能以及其他几个插件都可以与 STOMP 一起使用,尽管可能存在一些限制或需要调整默认设置。
启用插件
STOMP 插件包含在 RabbitMQ 发行版中。在客户端成功连接之前,必须使用 rabbitmq-plugins 启用它。
rabbitmq-plugins enable rabbitmq_stomp
插件配置
TCP 监听器
当未指定配置时,STOMP 适配器将监听所有接口的 61613 端口,并使用默认的用户名/密码 guest/guest。
要更改监听端口,请编辑您的配置文件,为 rabbitmq_stomp 应用程序添加一个 tcp_listeners 变量。
例如,一个将监听端口更改为 12345 的最小化配置文件如下所示:
stomp.listeners.tcp.1 = 12345
而一个仅监听 localhost(IPv4 和 IPv6)的配置将如下所示
stomp.listeners.tcp.1 = 127.0.0.1:61613
stomp.listeners.tcp.2 = ::1:61613
TCP 监听器选项
该插件支持 TCP 监听器选项配置。
这些设置使用通用前缀 stomp.tcp_listen_options,并控制 TCP 缓冲区大小、入站 TCP 连接队列长度、是否启用 TCP keepalives 等。详情请参阅网络指南。
stomp.listeners.tcp.1 = 127.0.0.1:61613
stomp.listeners.tcp.2 = ::1:61613
stomp.tcp_listen_options.backlog = 4096
stomp.tcp_listen_options.recbuf = 131072
stomp.tcp_listen_options.sndbuf = 131072
stomp.tcp_listen_options.keepalive = true
stomp.tcp_listen_options.nodelay = true
stomp.tcp_listen_options.exit_on_close = true
stomp.tcp_listen_options.send_timeout = 120
TLS 支持
要在 STOMP 连接中使用 TLS,必须在代理中配置 TLS。要启用支持 TLS 的 STOMP 连接,请使用 stomp.listeners.ssl.* 配置键为 STOMP 添加 TLS 监听器。
该插件将使用核心 RabbitMQ 服务器证书和密钥(就像 AMQP 0.9.1 和 AMQP 1.0 监听器一样)
ssl_options.cacertfile = /path/to/tls/ca_certificate.pem
ssl_options.certfile = /path/to/tls/server_certificate.pem
ssl_options.keyfile = /path/to/tls/server_key.pem
ssl_options.verify = verify_peer
ssl_options.fail_if_no_peer_cert = true
stomp.listeners.tcp.1 = 61613
# default TLS-enabled port for STOMP connections
stomp.listeners.ssl.1 = 61614
此配置在端口 61613 上创建一个标准 TCP 监听器,在端口 61614 上创建一个 TLS 监听器。
设置 TLS 监听器后,您可能希望停用所有非 TLS 监听器。这可以通过以下方式配置:
stomp.listeners.tcp = none
stomp.listeners.ssl.1 = 61614
默认用户
如果配置了默认用户,RabbitMQ STOMP 适配器允许 CONNECT 帧省略 login 和 passcode 头部。
要配置默认用户名和密码,请在 rabbitmq_stomp 应用程序配置中添加一个 default_user 部分。例如:
stomp.default_user = guest
stomp.default_pass = guest
上面的配置示例将 guest/guest 设为默认的用户名/密码对。
使用 TLS/x509 客户端证书进行身份验证
该插件可以通过从客户端的 TLS (x509) 证书中提取名称来对 TLS 连接进行身份验证,而无需使用密码。
为了安全起见,必须将服务器的TLS 选项配置为 fail_if_no_peer_cert 设置为 true,并将 verify 设置为 verify_peer,以强制所有 TLS 客户端提供可验证的客户端证书。
要开启此功能,请将 rabbitmq_stomp 应用程序的 ssl_cert_login 设置为 true。例如:
stomp.ssl_cert_login = true
默认情况下,这将把用户名设置为证书主体(Subject)的标识名(Distinguished Name)的 RFC4514 风格字符串形式,类似于 OpenSSL 的 "-nameopt RFC2253" 选项产生的结果。
要改用通用名称(Common Name),请添加:
ssl_cert_login_from = common_name
到您的配置中。
请注意
- 经过身份验证的用户必须存在于已配置的身份验证/授权后端中。
- 客户端不得提供
login和passcode头部。
隐式连接
如果您配置了默认用户或使用了 SSL 客户端证书身份验证,还可以允许客户端完全省略 CONNECT 帧。在此模式下,如果会话上发送的第一帧不是 CONNECT,客户端将自动以默认用户或 SSL 证书中提供的用户身份连接。
要启用隐式连接,请将 rabbit_stomp 应用程序的 implicit_connect 设置为 true。例如:
stomp.default_user = guest
stomp.default_pass = guest
stomp.implicit_connect = true
默认情况下,隐式连接未启用。
注意:触发隐式连接的客户端不会从服务器收到 CONNECTED 帧。
代理协议
STOMP 插件支持 proxy protocol。此功能默认关闭。要为 STOMP 客户端开启它:
stomp.proxy_protocol = true
有关代理协议的更多信息,请参阅 网络指南。
帧大小限制
默认情况下,帧大小限制为 4Mb。当帧超过此限制时,将视为错误并关闭连接。
stomp.max_frame_size = 4 * 1024 * 1024
目的地
STOMP 规范未规定代理必须支持何种类型的目的地,SEND 和 MESSAGE 帧中 destination 头的值由具体的代理决定。RabbitMQ STOMP 适配器支持多种不同的目的地类型:
/exchange-- 发送(SEND)到任意路由键,并订阅(SUBSCRIBE)到任意绑定模式;/queue-- 发送(SEND)和订阅(SUBSCRIBE)到由 STOMP 网关管理的队列;/amq/queue-- 发送(SEND)和订阅(SUBSCRIBE)到在 STOMP 网关之外创建的队列;/topic-- 发送(SEND)和订阅(SUBSCRIBE)到瞬时和持久主题;/temp-queue/-- 创建临时队列(仅限在reply-to头部中使用)。
AMQP 0-9-1 语义
MESSAGE 帧上的 destination 头部设置方式就像该消息源自 SEND 帧一样:
- 发布到默认交换机的消息被赋予
/queue/queuename 的目的地; - 发布到
amq.topic的消息被赋予/topic/routing_key 的目的地; - 所有其他消息被赋予
/exchange/exchange_name[/routing_key] 的目的地。
如果 queuename、exchange_name 或 routing_key 中包含 /、% 或非 ASCII 字节,它们将被替换为 %dd 序列,其中 dd 是该字节的十六进制代码。
由于这些规则,MESSAGE 帧上的目的地可能与发布它的 SEND 帧上的目的地不完全匹配。
不同的目的地具有不同的队列参数默认值。它们可以通过头部显式控制,具体说明见本指南后续部分。
交换机目的地
可以使用以 /exchange 为前缀的目的地访问任何交换机/队列或交换机/路由键组合。
对于 SUBSCRIBE 帧,可以使用 /exchange/<name>[/<pattern>] 格式的目的地。该目的地:
- 在
<name>交换机上创建一个独占的、自动删除的队列; - 如果提供了
<pattern>,则使用<pattern>将队列绑定到<name>交换机;以及 - 针对当前 STOMP 会话在该队列上注册订阅。
对于 SEND 帧,可以使用 /exchange/<name>[/<routing-key>] 格式的目的地。该目的地:
- 发送消息到
<name>交换机,并使用<routing-key>作为路由键。
注意:交换机目的地不适合从现有队列消费消息。每个订阅者都会创建一个新队列,并使用提供的路由键绑定到指定的交换机。要使用现有队列,请使用 /amq/queue 目的地。
队列目的地
对于简单队列,可以使用 /queue/<name> 格式的目的地。
队列目的地将每条消息传递给最多一个订阅者。当没有订阅者存在时发送的消息将被排队,直到有订阅者连接到该队列。
AMQP 0-9-1 语义
对于 SUBSCRIBE 帧,这些目的地会创建一个共享队列 <name>。并为当前 STOMP 会话创建针对队列 <name> 的订阅。
对于 SEND 帧,在当前会话中第一次发送到此目的地时,会创建共享队列 <name>,之后不再创建。消息通过默认交换机发送,路由键为 <name>。
如果未指定队列参数,队列将被视为持久、非独占且非自动删除的。
AMQ 队列目的地
要寻址在 STOMP 适配器外部创建的现有队列,可以使用 /amq/queue/<name> 格式的目的地。
AMQP 0-9-1 语义
对于 SEND 和 SUBSCRIBE 帧,都不会创建队列。对于 SUBSCRIBE 帧,如果队列不存在,则视为错误。
对于 SEND 帧,消息通过默认交换机直接发送到名为 <name> 的现有队列。
对于 SUBSCRIBE 帧,将为当前 STOMP 会话创建针对现有队列 <name> 的订阅。
如果未指定队列参数,队列将被视为持久、非独占且非自动删除的。
主题目的地
STOMP 客户端使用最广泛的目的地类型可能是 /topic/<name>。它们根据订阅者模式对发布的消息执行主题匹配,并可以将消息路由到多个订阅者(每个订阅者获得一份副本)。主题目的地支持 AMQP 0-9-1 主题交换机的所有路由模式。
发送到没有活跃订阅者的主题目的地的消息会被简单地丢弃。
AMQP 0-9-1 语义
对于 SEND 帧,消息发送到 amq.topic 交换机,路由键为 <name>。
对于 SUBSCRIBE 帧,会创建一个自动删除、非持久的队列,并使用路由键 <name> 绑定到 amq.topic 交换机。并针对该队列创建订阅。
可以使用 stomp.default_topic_exchange 配置设置来指定与 amq.topic 不同的默认交换机:
stomp.default_topic_exchange = some.exchange
持久主题订阅
STOMP 适配器支持持久主题订阅。持久订阅允许客户端根据需要与 STOMP 代理断开连接并重新连接,而不会丢失发送到该主题的消息。
主题本身既不是持久的也不是瞬时的,而是订阅分为持久或瞬时。对于给定的主题,持久和瞬时订阅可以共存。
创建持久订阅
要创建持久订阅,请在 SUBSCRIBE 帧中将 durable 头部设置为 true。为了与早期插件版本向后兼容,persistent 也被支持作为 durable 的别名。
在为主题目的地创建持久订阅时,请将 auto-delete 设置为 false,以确保当最后一个订阅者断开连接时,支持您订阅的队列不会被删除。
创建持久订阅时,必须指定 id 头部。例如:
SUBSCRIBE
destination:/topic/my-durable
id:1234
durable:true
auto-delete:false
AMQP 0-9-1 语义
对于 SEND 帧,消息发送到 amq.topic 交换机,路由键为 <name>。
对于 SUBSCRIBE 帧,会为每个不同的订阅 ID x 目的地对创建一个共享队列,并使用路由键 <name> 绑定到 amq.topic 交换机。并针对该队列创建订阅。
注意:可以使用 stomp.default_topic_exchange 配置设置来指定与 amq.topic 不同的默认交换机。
删除持久订阅
要永久删除持久订阅,请发送该订阅 ID 的 UNSUBSCRIBE 帧,并使用与订阅时相同的 durable 和 auto-delete 头部值。
例如:
UNSUBSCRIBE
id:1234
durable:true
auto-delete:false
临时队列目的地
临时队列目的地允许您在 SEND 帧的 reply-to 头部中定义临时目的地。
临时队列由代理管理,它们的身份对每个会话是私有的——无需为不同会话中的临时队列选择不同的名称。
要使用临时队列,请在 SEND 帧上放置 reply-to 头部,并使用以 /temp-queue/ 开头的头部值。例如:
SEND
destination:/queue/reply-test
reply-to:/temp-queue/foo
Hello World!
此帧会创建一个会话私有的临时队列(具有生成的名称),并自动订阅该队列。另一个使用 reply-to:/temp-queue/foo 的会话将创建另一个新的、独立的队列。
内部订阅 ID 是字符串 /temp-queue/ 与临时队列名称的串联(在本例中为 /temp-queue/foo)。该订阅 ID 可用于识别回复消息。无法通过 destination 头部识别回复消息,因为它将不同于 reply-to 头部中的值。内部订阅使用自动确认模式,且无法取消。
/temp-queue/ 目的地不是接收客户端在发送回复时使用的目的地名称。相反,接收客户端可以从 MESSAGE 帧的 reply-to 头部中获取(真实的)回复目的地队列名称。然后,该回复目的地名称可用作接收到 MESSAGE 后所发送回复的 SEND 帧中 destination 头部的值。
回复目的地队列名称是不透明的,无法从 /temp-queue/ 名称中推断出来。
SEND 和 SUBSCRIBE 帧的 destination 头部不得包含 /temp-queue 目的地。消息无法发送到 /temp-queue 目的地,且回复队列的订阅是自动创建的。
AMQP 0-9-1 语义
每个 /temp-queue/ 对应一个独立的匿名、独占、自动删除队列。因此,无需显式清理回复队列。
为主题和交换机目的地生成用户自定义的队列名称
当订阅 exchange 或 topic 目的地时,RabbitMQ 默认会生成一个队列名称。可以使用 x-queue-name 头部提供自定义名称:
SUBSCRIBE
destination:/topic/alarms
x-queue-name:my-alarms-queue
使用 STOMP 控制 RabbitMQ 队列参数
队列属性可以通过 STOMP 头部进行控制:
durable(别名为persistent)auto-deleteexclusive
以及用于控制死信、队列和消息 TTL、队列限制等的可选参数("x-arguments"):
x-dead-letter-exchangex-dead-letter-routing-keyx-expiresx-message-ttlx-max-lengthx-max-length-bytesx-max-age(仅适用于 流)x-stream-max-segment-size-bytes(仅适用于 流)x-overflowx-max-priorityx-queue-type(用于声明仲裁队列和流)
每个头部的含义与通过 AMQP 0-9-1 声明队列时的含义相同。详情请参考其余文档。
在 STOMP 中使用策略
RabbitMQ 策略允许对队列和交换机进行灵活、集中的属性配置。策略可以应用于 STOMP 插件使用的队列。
策略使得在 STOMP 中可以使用更多的 RabbitMQ 特性:
STOMP 插件创建的所有服务器命名队列都带有 stomp- 前缀,这使得在策略中匹配这些队列变得容易。例如,要将 STOMP 队列长度限制为 1000 条消息,请创建以下策略:
rabbitmqctl set_policy stomp-queues "^stomp-" '{"max-length":1000}' --apply-to queues
使用 Windows 上的 rabbitmqctl.bat:
rabbitmqctl.bat set_policy stomp-queues "^stomp-" "{""max-length"":1000}" --apply-to queues
请注意,一个队列一次只能应用一个策略,因此要指定多个参数(例如队列长度限制和死信),需要将它们放入单个策略中。
协议扩展和限制
RabbitMQ STOMP 适配器放宽了对 CONNECT 的协议限制,并在某些帧上支持多个非标准头部。这些额外头部提供了对 STOMP 规范中未描述的功能的访问。此外,我们禁止使用一些保留给服务器使用的头部。详情如下。
连接和虚拟主机
STOMP 1.1 中的 CONNECT (或 STOMP) 帧有一个强制性的 host 头部(用于选择连接所使用的虚拟主机)。RabbitMQ 适配器允许该头部可选。
省略时,默认为默认虚拟主机 (/)。要配置不同的默认虚拟主机,请在 rabbitmq_stomp 应用程序配置中添加一个 default_vhost 部分,例如:
stomp.default_vhost = /
如果指定了 host 头部,它必须是 RabbitMQ 服务器已知的虚拟主机之一,否则连接将被拒绝。即使连接时协商的是 STOMP 1.0 版本,host 头部也会被遵循。
消息持久性
在 SEND 帧上,STOMP 适配器支持包含 persistent 头部。
将 persistent 头部设置为 true 可以使消息持久化。
对于带有 persistent:true 的 SEND 帧,在从代理接收到确认之前,不会发送回执。有关持久消息确认的具体语义,请参见此处。
持久消息的 MESSAGE 帧将包含一个 persistent:true 头部。
ACK 和 NACK
RabbitMQ STOMP 插件支持 auto、client 和 client-individual 订阅头部,它们会影响 ACK 和 NACK 操作的工作方式。
auto 模式使用自动确认。client 模式是手动(由客户端驱动)一次确认多条消息。client-individual 用于逐条手动确认消息。
NACK 帧可以选择性地携带 requeue 头部,该头部控制消息是被重新排队还是被丢弃/进入死信队列。默认值为 true。
预取
默认情况下,所有订阅的预取计数被设置为无限。这可以通过将 SUBSCRIBE 帧上的 prefetch-count 头部设置为所需的整数计数来控制。
流支持
SUBSCRIBE 帧支持 x-stream-offset 头部,以指定从流中的哪个偏移量开始消费。典型的流订阅帧如下所示:
SUBSCRIBE
destination:/amq/queue/my-stream
ack:client
prefetch-count:10
x-stream-offset:next
注意,ack 和 prefetch-count 头部也是必需的。x-stream-offset 头部与 AMQP 0.9.1 中的语义相同,可能的值包括:
first:从流中第一个可用消息开始消费;last:从最后写入的消息块开始消费;next:从流的末尾开始消费(注意,在有人向流发布消息之前,消费者将不会收到任何消息);offset=<offset-value>:从特定偏移量开始,例如offset=40000;timestamp=<unix-time>:从给定时间开始,例如timestamp=1619432061代表2021-04-26T10:14:21+00:00。
默认值为 next。
从流中传递消息时,消息偏移量(即消息在流中的位置)包含在 MESSAGE 帧的 x-stream-offset 头部中。
同时支持流过滤。流协议是与流交互的首选方式,但大多数功能也适用于其他协议。流过滤也不例外,它在 STOMP 中的工作方式与在 AMQP 中的工作方式相同:
- 声明:可以在订阅时创建流。将
x-queue-type头部设置为stream,并使用x-stream-filter-size-bytes头部设置过滤器大小(可选)。 - 发布:使用
x-stream-filter-value头部为出站消息设置过滤器值。 - 消费:使用
x-stream-filter头部设置期望的过滤器值(用逗号分隔多个值),并可选择使用x-stream-match-unfiltered头部(true或false)以同时接收没有任何过滤器值的消息(默认为false)。应用程序还必须实现客户端过滤,因为仍然可能收到不符合过滤器值标准的消息。
禁止在 SEND 上设置的头部
禁止在 SEND 帧上设置 message-id 头部。该头部及其值由服务器在发送给客户端的 MESSAGE 帧上设置。
队列属性
SEND 帧还允许使用对应于发布消息时可用AMQP 属性的头部。这些头部也会在发送给客户端的 MESSAGE 帧上设置。
支持所有非弃用的 AMQP 0-9-1 属性(content-type, content-encoding, headers, delivery-mode, priority, correlation-id, reply-to, expiration, message-id, timestamp, type, user-id 和 app-id)。以下是特殊规则:
- STOMP 中的
amqp-message-id会转换为 AMQP 中的message-id,反之亦然。 reply-to头部会导致创建临时队列(参见上文的临时队列目的地)。- 一些以 x- 开头的 STOMP 头部会被转换为可选队列参数(见下文)。
可选队列属性
在 RabbitMQ 中,SEND 和 SUBSCRIBE 帧可以包含一组头部来配置队列行为,例如使用 TTL 或类似扩展。
支持的头部列表如下:
x-message-ttlx-expiresx-max-lengthx-max-length-bytesx-dead-letter-exchangex-dead-letter-routing-keyx-max-priority
例如,如果您想在 STOMP 中使用优先级队列,可以通过以下头部进行 SUBSCRIBE (或 SEND):
SUBSCRIBE
destination:/queue/my-priority-queue
x-max-priority:5
队列不可变性
一旦声明了队列,其属性就无法更改。可选参数可以通过策略进行修改。否则,必须删除并重新声明该队列。对于 STOMP 客户端和 AMQP 0-9-1 客户端,情况都是如此。