死信交换器
什么是死信交换器
队列中的消息可以被“死信化”(dead-lettered),这意味着当发生以下四种事件之一时,这些消息会被重新发布到另一个交换器中。
- 消息被 AMQP 1.0 接收者使用
rejected结果,或被 AMQP 0.9.1 消费者使用requeue参数设置为false的basic.reject或basic.nack拒绝接收;或者 - 消息由于每条消息 TTL(过期时间)而过期;或者
- 消息因为所在队列超过了长度限制而被丢弃;或者
- 消息返回到仲裁队列的次数超过了交付限制 (delivery-limit)。
如果整个队列过期,队列中的消息不会被死信化。
死信交换器 (DLX) 是普通的交换器。它们可以是任何常见的类型,并按常规方式声明。
如何配置死信化
对于任何给定的队列,客户端可以使用策略 (policies) 来定义 DLX。有几个与 DLX 相关的策略键,其中一些仅受仲裁队列支持,但两个关键的策略键是:
dead-letter-exchange:要使用的 DLX 名称dead-letter-routing-key:死信化消息时使用的路由键
策略键也可以由应用程序在队列声明时通过可选参数 (optional arguments) 进行设置。
强烈建议不要硬编码 x-arguments,因为它们无法在不重新部署应用程序的情况下进行更新,而策略可以在任何时候更新。
如果策略和参数都指定了 DLX,则参数中指定的优先级高于策略中指定的。
除了目标 DLX 名称外,还可以指定死信化消息时使用的路由键。如果没有设置路由键,则使用消息自身的路由键。
当指定了死信交换器时,除了对声明的队列具有常规的配置权限外,用户还必须对该队列拥有读取权限,并对死信交换器拥有写入权限。权限会在队列声明时进行验证。
使用策略配置死信交换器
要使用策略指定 DLX,请在策略定义中添加键“dead-letter-exchange”
- 使用 rabbitmqctl (bash)
- 使用 rabbitmqctl (PowerShell)
- 使用 rabbitmqadmin (bash)
- 使用 rabbitmqadmin (PowerShell)
- HTTP API
- Management UI
rabbitmqctl set_policy DLX ".*" '{"dead-letter-exchange":"my-dlx"}' --apply-to queues --priority 7
rabbitmqctl set_policy DLX ".*" "{""dead-letter-exchange"":""my-dlx""}" --apply-to queues --priority 7
rabbitmqadmin policies declare \
--name "DLX" \
--pattern ".*" \
--definition '{"dead-letter-exchange":"my-dlx"}' \
--apply-to "queues" \
--priority 7
rabbitmqadmin.exe policies declare ^
--name "DLX" ^
--pattern ".*" ^
--definition "{""dead-letter-exchange"":""my-dlx""}" ^
--apply-to "queues" ^
--priority 7
PUT /api/policies/%2f/DLX
{"pattern": ".*",
"definition": {"dead-letter-exchange":"my-dlx"},
"priority": 7,
"apply-to": "queues"}
导航到
Admin>Policies>Add / update a policy。在 Name 旁边输入策略名称(例如“DLX”),在 Pattern 旁边输入模式(此例中为“.*”),并使用
Apply to下拉菜单选择策略应应用于哪种实体(此例中为所有队列)。在
Policy旁的第一行,将键设为“dead-letter-exchange”,值设为“my-dlx”。点击
Add policy。
前面的示例声明了一个名为“DLX”的策略,该策略适用于所有队列(无论类型如何),并将名为“my-dlx”的交换器配置为死信目标。这只是一个示例,在实践中,通常会看到多个策略分别应用于队列的子集。
同样,可以通过添加键“dead-letter-routing-key”到策略中来指定明确的路由键
- 使用 rabbitmqctl (bash)
- 使用 rabbitmqctl (PowerShell)
- 使用 rabbitmqadmin (bash)
- HTTP API
- Management UI
rabbitmqctl set_policy DLX ".*" '{"dead-letter-exchange":"my-dlx", "dead-letter-routing-key":"my-routing-key"}' --apply-to queues --priority 7
rabbitmqctl set_policy DLX ".*" "{""dead-letter-exchange"":""my-dlx"", ""dead-letter-routing-key"":""my-routing-key""}" --apply-to queues --priority 7
rabbitmqadmin policies declare --name=DLX --pattern=".*" --definition='{"dead-letter-exchange":"my-dlx", "dead-letter-routing-key":"my-routing-key"}' --apply-to=queues --priority=7
PUT /api/policies/%2f/DLX
{"pattern": ".*",
"definition": {"dead-letter-exchange":"my-dlx"},
"priority": 7,
"apply-to": "queues"}
导航到
Admin>Policies>Add / update a policy。在 Name 旁边输入策略名称(例如“DLX”),在 Pattern 旁边输入模式(此例中为“.*”),并使用
Apply to下拉菜单选择策略应应用于哪种实体(此例中为所有队列)。在
Policy旁的第一行,将键设为“dead-letter-exchange”,值设为“my-dlx”。点击
Add policy。
使用可选队列参数配置死信交换器
强烈建议不要硬编码 x-arguments,因为它们无法在不重新部署应用程序并删除队列后再重新声明的情况下进行更新,而策略可以在任何时候更新。
要为队列设置 DLX,请在声明队列时指定可选的 x-dead-letter-exchange 参数。其值必须是同一虚拟主机中的交换器名称
channel.exchangeDeclare("some.exchange.name", "direct");
// Important: prefer using policies over hardcoded x-arguments
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-dead-letter-exchange", "some.exchange.name");
channel.queueDeclare("myqueue", false, false, false, args);
上面的代码声明了一个名为 some.exchange.name 的新交换器,并将此交换器设置为新创建队列的死信交换器。注意,在声明队列时不需要预先声明交换器,但当消息需要死信化时,该交换器必须已经存在。如果届时它不存在,消息将被静默丢弃。
除了目标 DLX 名称外,还可以指定死信化消息时使用的路由键。如果没有设置路由键,则使用消息自身的路由键。
// Important: prefer using policies over hardcoded x-arguments.
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-dead-letter-exchange", "some.exchange.name");
args.put("x-dead-letter-routing-key", "some-routing-key");
当指定了死信交换器时,除了对声明的队列具有常规的配置权限外,用户还必须对该队列拥有读取权限,并对死信交换器拥有写入权限。权限会在队列声明时进行验证。
死信化消息的路由
死信化的消息会路由到其死信交换器,路由方式为:
- 使用为该队列指定的路由键;或者,如果未设置,
- 使用它们最初发布时所使用的原始路由键
例如,如果您将一条消息发布到带有 foo 路由键的交换器,并且该消息被死信化,它将以 foo 路由键发布到其死信交换器。如果该消息最初落入的队列是通过设置 x-dead-letter-routing-key 为 bar 而声明的,那么消息将以 bar 路由键发布到其死信交换器。
注意,如果未为队列设置特定的路由键,则队列上的消息将使用其所有原始路由键进行死信化。这包括由 CC 和 BCC 头添加的路由键(有关这两个头的详细信息,请参阅发送者选择的分发 (Sender-selected distribution))。
死信循环
可能会形成消息死信循环,即同一条消息两次到达同一个队列。例如,当队列将消息“死信化”到默认交换器且未指定死信路由键时,就会发生这种情况。为了防止 RabbitMQ 内部出现自动无限循环,如果在整个循环中没有发生拒绝,RabbitMQ 将检测到循环并丢弃该消息。
安全性
死信化是一种消息发布形式,和任何发布形式一样,它在某些情况下可能会失败。例如,如果死信化被配置为使用一个没有在线仲裁的仲裁队列,发布将失败,执行死信化的节点将记录类似以下的消息:
Cannot forward any dead-letter messages from source quorum queue 'qq.input' in vhost 'my-vhost'
with configured dead-letter-exchange exchange 'amq.topic' in vhost 'my-vhost'
and configured dead-letter-routing-key 'my-app.events.type.abc'
使用发布者确认重新发布
默认情况下,死信消息在内部重新发布时不会开启发布者确认。因此,在集群 RabbitMQ 环境中使用 DLX 不能保证是安全的。消息在发布到 DLX 目标队列后会立即从原始队列中移除。这确保了不会出现导致代理资源耗尽的消息积压情况。但是,如果目标队列无法接受消息,消息可能会丢失。
仲裁队列支持至少一次 (at-least-once) 的死信化,此时消息会在内部开启发布者确认的情况下重新发布。
死信化对消息的影响
死信化消息会修改其标头:
- 交换器名称被替换为最新的死信交换器名称
- 路由键可能会被替换为执行死信化的队列中指定的路由键(即配置的
dead-letter-routing-key), - 如果发生上述情况,
CC头也将被移除,并且 - 根据发送者选择的分发,
BCC头将被移除
单条消息可以被多次死信化。每次消息被死信化时,该事件都会记录在消息头中。为了防止标头无限制增长,死信事件历史记录会按 {Queue, Reason} 对进行压缩。
AMQP 1.0 消息将包含一个消息注释,其中符号键为 x-opt-deaths,值为映射 (map) 的数组。AMQP 0.9.1 消息将包含一个值为数组的 x-death 头。AMQP 1.0 和 AMQP 0.9.1 中的数组均按时间顺序排列,即最近的死信事件记录在数组的第一个元素中。
下表描述了 AMQP 1.0 映射键值对和 AMQP 0.9.1 数组元素表。所有 AMQP 1.0 键均为 symbol 类型。AMQP 1.0 客户端不得依赖映射中键值对的顺序。
| AMQP 1.0 键 | AMQP 1.0 值类型 | AMQP 0.9.1 键 | AMQP 0.9.1 值类型 | 描述 |
|---|---|---|---|---|
| queue | string | queue | longstr | 消息被死信化的队列名称。 |
| reason | symbol | reason | longstr | 消息被死信化的原因(见下文)。 |
| count | ulong | count | long | 消息因该原因从该队列被死信化的次数。 |
| first-time | timestamp | 消息第一次因该原因从该队列被死信化的时间。 | ||
| last-time | timestamp | 消息最后一次因该原因从该队列被死信化的时间。 | ||
| time | timestamp | 消息第一次因该原因从该队列被死信化的时间。 | ||
| exchange | string | exchange | longstr | 消息在第一次因该原因从该队列被死信化之前所发布到的交换器。 |
| routing-keys | array of string | routing-keys | array of longstr | 消息在第一次因该原因从该队列被死信化之前的路由键(包括 CC 但不包括 BCC)。 |
| ttl | uint | 消息在第一次因该原因从该队列被死信化之前,AMQP 1.0 标头的 ttl(以毫秒为单位的存活时间)。 | ||
| original-expiration | longstr | 消息在第一次因该原因从该队列被死信化之前,其原始的 expiration 属性。 |
AMQP 1.0 的 ttl 和 AMQP 0.9.1 的 original-expiration 是可选的,记录它们是因为原始消息的 TTL 在死信化时会从消息中移除,以防止其在后续路由到的任何队列中再次过期。
reason 是描述消息为何被死信化的名称,包括以下几种:
rejected:消息被拒绝expired:消息 TTL 已过期maxlen:超过了最大允许队列长度delivery_limit:消息返回次数超过了限制(由仲裁队列的策略参数 delivery-limit 设置)。
此外,对于第一次死信化事件,会添加以下六个 AMQP 1.0 消息注释或 AMQP 0.9.1 标头:
x-first-death-queue:消息第一次被死信化的队列。x-first-death-reason:消息第一次被死信化的原因。x-first-death-exchange:消息第一次被死信化之前所发布到的交换器。x-last-death-queue:消息最后一次被死信化的队列。x-last-death-reason:消息最后一次被死信化的原因。x-last-death-exchange:消息最后一次被死信化之前所发布到的交换器。
x-first-* 注释永远不会被修改。每当消息随后被死信化时,x-last-* 注释都会被更新。