死信交换器
什么是死信交换器
来自队列的消息可以被“死信化”,这意味着当发生以下四种事件之一时,这些消息会被重新发布到一个交换器。
- AMQP 1.0 接收者使用
rejected结果负面确认消息,或者 AMQP 0.9.1 消费者使用basic.reject或basic.nack并将requeue参数设置为false,或者 - 消息因 每条消息的 TTL 而过期,或者
- 消息因其队列超出 长度限制 而被丢弃,或者
- 消息在仲裁队列(quorum queue)中被退回的次数超过了 传递限制(delivery-limit)。
如果整个 队列过期,队列中的消息**不会**被死信化。
死信交换器(DLX)是普通的交换器。它们可以是任何常规类型,并且被声明为常规交换器。
如何配置死信化
对于任何给定的队列,客户端可以使用 策略 定义一个 DLX。有几个与 DLX 相关的策略键,包括一些仅仲裁队列支持的键,但两个关键的键是:
dead-letter-exchange:要使用的 DLX 的名称dead-letter-routing-key:死信化消息时要使用的路由键
策略键也可以通过 可选参数 在声明队列时由应用程序设置。
强烈建议不要使用硬编码的 x-arguments,因为它们在不重新部署应用程序的情况下无法更新,而策略可以随时更新。
如果策略和参数都指定了 DLX,则参数中指定的 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 标头添加的路由键(有关这两个标头的详细信息,请参阅 发送方选择的分发)。
死信循环
可能会形成消息死信化的循环,即同一条消息两次到达同一个队列。例如,当一个队列将消息死信化到默认交换器而不指定死信路由键时,就可能发生这种情况。为了防止 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'
使用 Publisher Confirms 重新发布
默认情况下,死信化的消息在内部*不*启用 Publisher Confirms 进行重新发布。因此,在集群的 RabbitMQ 环境中使用 DLX 不能保证安全。消息在发布到 DLX 目标队列后立即从原始队列中删除。这确保了不会有消息堆积过多而耗尽 Broker 资源的可能性。但是,如果目标队列不可用以接受消息,则可能会丢失消息。
仲裁队列支持*至少一次死信化*(at-least-once dead-lettering),其中消息以内部启用的 Publisher Confirms 进行重新发布。
死信化对消息的影响
死信化消息会修改其标头:
- 交换器名称被替换为最新死信交换器的名称。
- 路由键可能会被执行死信化的队列指定的路由键替换(即配置的
dead-letter-routing-key)。 - 如果发生上述情况,
CC标头也将被删除,并且 - 根据 发送方选择的分发,
BCC标头将被删除。
一条消息可以被死信化多次。每次消息被死信化时,此事件都会记录在消息标头中。为了防止标头无限制地增长,死信事件历史会通过 {Queue, Reason} 对进行压缩。
AMQP 1.0 消息将包含一个带有符号键 x-opt-deaths 的消息注解,其值为一个数组,数组中的元素是映射。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 header 的 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-* 注解都会被更新。