死信交换
什么是死信交换
当以下四种事件中的任何一种发生时,队列中的消息可以被“死信”,这意味着这些消息将在重新发布到一个交换机。
- 消息被 负向确认 由使用
rejected
结果的 AMQP 1.0 接收器或使用basic.reject
或basic.nack
并将requeue
参数设置为false
的 AMQP 0.9.1 消费者,或者 - 消息由于 每消息 TTL 而过期,或者
- 消息因其队列超出 长度限制 而被丢弃,或者
- 消息被返回到仲裁队列的次数超过了 传递限制。
如果整个 队列过期,则队列中的消息不会被死信。
死信交换 (DLX) 是普通的交换机。它们可以是任何常见的类型,并像普通交换机一样声明。
对于任何给定的队列,DLX 可以由客户端使用 队列的参数 定义,或者在服务器中使用 策略 定义。在策略和参数都指定 DLX 的情况下,参数中指定的 DLX 会覆盖策略中指定的 DLX。
建议使用策略进行配置,因为它允许重新配置 DLX,而无需重新部署应用程序。
使用策略配置死信交换
要使用策略指定 DLX,请将键“dead-letter-exchange”添加到策略定义中。例如
rabbitmqctl |
|
---|---|
rabbitmqctl(Windows) |
|
上面的策略将 DLX “my-dlx” 应用于所有队列。这只是一个示例,在实践中,不同的队列集通常使用不同的死信设置(或根本不使用)。
类似地,可以通过将键“dead-letter-routing-key”添加到策略中来指定显式路由键。
策略也可以使用管理插件定义,有关详细信息,请参见 策略文档。
使用可选队列参数配置死信交换
要为队列设置 DLX,请在声明队列时指定可选的 x-dead-letter-exchange
参数。该值必须是同一个虚拟主机中的交换机名称
channel.exchangeDeclare("some.exchange.name", "direct");
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
的新交换机,并将这个新交换机设置为新创建的队列的死信交换机。请注意,交换机不必在声明队列时声明,但应在需要死信消息之前存在。如果它丢失,则消息将被静默丢弃。
您还可以指定在死信消息时使用的路由键。如果没有设置路由键,将使用消息自身的路由键。
args.put("x-dead-letter-routing-key", "some-routing-key");
当指定死信交换机时,除了对声明的队列进行通常的配置权限外,用户还必须对该队列具有读权限,对死信交换机具有写权限。权限在声明队列时进行验证。
路由死信消息
死信消息将被路由到它们的死信交换机,方法如下
- 使用为它们所在的队列指定的路由键;或者,如果没有设置,
- 使用它们最初发布时的相同路由键
例如,如果您将一条消息发布到具有 foo
路由键的交换机,并且该消息被死信,它将使用 foo
路由键发布到其死信交换机。如果消息最初所在的队列使用 x-dead-letter-routing-key
设置为 bar
声明,则消息将使用 bar
路由键发布到其死信交换机。
请注意,如果为队列没有设置特定的路由键,则队列上的消息将使用所有原始路由键进行死信。这包括由 CC
和 BCC
标头添加的路由键(有关这两个标头的详细信息,请参阅 发送方选择的分配)。
死信循环
有可能形成一个消息死信循环,其中相同的消息两次到达相同的队列。例如,当队列“死信”消息到默认交换机而没有指定死信路由键时,就会发生这种情况。为了防止 RabbitMQ 中自动无限消息循环,RabbitMQ 会检测到循环,并在整个循环中没有拒绝的情况下丢弃消息。
安全
默认情况下,死信消息将在没有内部打开生产者 确认 的情况下重新发布。因此,在集群式 RabbitMQ 环境中使用 DLX 并不保证安全。消息在发布到 DLX 目标队列后立即从原始队列中删除。这确保了不会出现可能耗尽代理资源的过量消息积压。但是,如果目标队列不可用以接收消息,则消息可能会丢失。
从 RabbitMQ 3.10 开始,仲裁队列支持 至少一次死信,其中消息在内部打开生产者确认的情况下重新发布。
死信对消息的影响
死信消息会修改其标头
- 交换机名称将被最新的死信交换机名称替换
- 路由键可能会被执行死信的队列中指定的路由键替换(即配置的
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 | string 数组 | routing-keys | 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-*
注释就会更新。