消息可能会被不成功地送达(例如,如果用于消费它们的交易会话回滚)。此类消息将返回到其队列,准备重新送达。但是,这意味着消息可能会反复不成功地送达,从而无限期地保留在队列中,堵塞系统。

有两种方法可以处理这些未送达的消息

  • 延迟重发。

    可以延迟消息的重发。这使客户端有一些时间从任何瞬态故障中恢复,并防止其网络或 CPU 资源过载。

  • 死信地址。

    还可以配置一个死信地址,以便在指定次数的不成功送达后,将消息从其队列中删除并发送到死信地址。这些消息将不会从该队列中再次送达。

两种选项可以结合使用,以实现最大的灵活性。

1. 延迟重发

在客户端经常失败或回滚的情况下,延迟重发通常很有用。如果没有延迟重发,系统可能会进入“震荡”状态,送达尝试、客户端回滚和送达尝试会无限期地快速连续进行,消耗宝贵的 CPU 和网络资源。

#持久化重发

每次重发时都会存储两个日志更新记录。一个是发生的送达次数,另一个是在使用计划的重发时的情况。

建议在使用非常短的重发延迟的情况下将 max-redelivery-records=1 保持在 1,因为您将在日志上创建不必要的记录。

1.1. 配置延迟重发

延迟重发是在地址设置配置中定义的

<!-- delay redelivery of messages for 5s -->
<address-setting match="exampleQueue">
   <!-- default is 1.0 -->
   <redelivery-delay-multiplier>1.5</redelivery-delay-multiplier>
   <!-- default is 0 (no delay) -->
   <redelivery-delay>5000</redelivery-delay>
   <!-- default is 0.0) -->
   <redelivery-collision-avoidance-factor>0.15</redelivery-collision-avoidance-factor>
   <!-- default is redelivery-delay * 10 -->
   <max-redelivery-delay>50000</max-redelivery-delay>
</address-setting>

如果指定了 redelivery-delay,Apache ActiveMQ Artemis 将等待此延迟,然后再重新送达消息。

默认情况下,没有重发延迟 (redelivery-delay 设置为 0)。

其他后续消息将定期送达,只有取消的消息将在延迟后异步返回到队列。

您可以指定一个乘数 (redelivery-delay-multiplier),它将对 redelivery-delay 起作用。每次重新送达消息时,延迟时间将等于前一次延迟 * redelivery-delay-multiplier。可以设置 max-redelivery-delay 来防止延迟变得过大。max-redelivery-delay 默认值为 redelivery-delay * 10。

例子

  • redelivery-delay=5000, redelivery-delay-multiplier=2, max-redelivery-delay=15000, redelivery-collision-avoidance-factor=0.0

    1. 送达尝试 1. (不成功)

    2. 等待延迟时间:5000

    3. 送达尝试 2. (不成功)

    4. 等待延迟时间:10000 // (5000 * 2) < max-delay-period。使用 10000

    5. 送达尝试 3: (不成功)

    6. 等待延迟时间:15000 // (10000 * 2) > max-delay-period: 使用 max-delay-delivery

可以使用地址通配符为一组地址配置重发延迟 (请参阅 了解通配符语法),因此您无需为每个地址单独指定重发延迟。

redelivery-delay 也可以通过配置 redelivery-collision-avoidance-factor 来修改。此因素将在随机情况下变为正值或负值,以控制最终值是否会增加或减少 redelivery-delay。然后,它将乘以 0.0 到 1.0 之间的随机数。然后将此结果乘以 redelivery-delay,然后添加到 redelivery-delay 以得出最终值。

该算法可能听起来很复杂,但底线很简单:您选择的 redelivery-collision-avoidance-factor 越大,redelivery-delay 的方差就越大。redelivery-collision-avoidance-factor 必须在 0.0 到 1.0 之间。

例子

  • redelivery-delay=1000, redelivery-delay-multiplier=1, max-redelivery-delay=15000, redelivery-collision-avoidance-factor=0.5, (使用 java.util.Random 选择的粗体值)

    1. 送达尝试 1. (不成功)

    2. 等待延迟时间:875 // 1000 + (1000 * ((0.5 * -1) * .25)

    3. 送达尝试 2. (不成功)

    4. 等待延迟时间:1375 // 1000 + (1000 * ((0.5 * 1) * .75)

    5. 送达尝试 3: (不成功)

    6. 等待延迟时间:975 // 1000 + (1000 * ((0.5 * -1) * .05)

此功能在有多个消费者在同一个队列上使用相同的外部系统 (例如数据库) 进行交易交互的环境中特别有用。如果在同时消费的消息中存在重叠的数据,则一个事务可能会成功,而所有其他事务可能会失败。如果这些失败的消息同时重新送达,则一个消费者成功,而其他消费者失败的此过程将继续。通过随机地用一个小的可配置量填充重发延迟,可以避免这些重发“冲突”。

1.2. 例子

请参阅 示例章节,其中显示了如何使用 JMS 配置和使用延迟重发。

2. 死信地址

为了防止客户端无限期地接收同一个未送达的消息 (无论是什么导致了不成功的送达),消息系统定义了死信地址:在指定次数的不成功送达尝试后,消息将从其队列中删除并发送到死信地址。

然后可以将任何此类消息转移到队列中,以便系统管理员稍后查看并采取措施。

Apache ActiveMQ Artemis 的地址可以分配一个死信地址。一旦消息在给定次数的尝试后未成功送达,它们将从其队列中删除并发送到相关的死信地址。这些死信消息可以在以后从死信地址消费,以供进一步检查。

2.1. 配置死信地址

死信地址是在地址设置配置中定义的

<!-- undelivered messages in exampleQueue will be sent to the dead letter address
deadLetterQueue after 3 unsuccessful delivery attempts -->
<address-setting match="exampleQueue">
   <dead-letter-address>deadLetterAddress</dead-letter-address>
   <max-delivery-attempts>3</max-delivery-attempts>
</address-setting>

如果没有指定 dead-letter-address,消息将在 max-delivery-attempts 次不成功尝试后被删除。

默认情况下,消息最多重新送达 10 次。将 max-delivery-attempts 设置为 -1 以实现无限次重新送达。

可以为一组匹配地址全局设置一个 死信地址,并且可以将 max-delivery-attempts 设置为 -1 以仅对特定地址设置允许无限次重新送达。

可以使用地址通配符为一组地址配置死信设置 (请参阅 了解通配符语法)。

2.2. 死信属性

死信消息会获得 特殊的属性

2.3. 自动创建死信资源

通常,会按其原始地址隔离未送达的消息。例如,发送到 stocks 地址但由于某种原因无法送达的消息可能会最终路由到 DLQ.stocks 队列,同样,发送到 orders 地址但无法送达的消息可能会路由到 DLQ.orders 队列。

使用这种模式可以轻松跟踪和管理未送达的消息。但是,它可能会在主要使用自动创建的地址和队列的环境中构成挑战。通常,这些环境中的管理员不想手动创建一个 address-setting 来配置 dead-letter-address,更不用说实际的 addressqueue 来保存未送达的消息了。

解决此问题的方案是将 auto-create-dead-letter-resources address-setting 设置为 true (默认情况下为 false),以便代理会自动创建 addressqueue 来处理未送达的消息。创建的 address 将是 dead-letter-address 中定义的 address。将在该 address 上创建一个 MULTICAST queue。它将使用发送消息的 address 来命名,并且将使用属性 _AMQ_ORIG_ADDRESS 定义一个过滤器,以便它只接收发送到相关 address 的消息。queue 名称可以使用前缀和后缀进行配置。请参阅下表中的相关设置

address-setting 默认

dead-letter-queue-prefix

DLQ.

dead-letter-queue-suffix

(空字符串)

这是一个配置示例

<address-setting match="#">
   <dead-letter-address>DLA</dead-letter-address>
   <max-delivery-attempts>3</max-delivery-attempts>
   <auto-create-dead-letter-resources>true</auto-create-dead-letter-resources>
   <dead-letter-queue-prefix></dead-letter-queue-prefix> <!-- override the default -->
   <dead-letter-queue-suffix>.DLQ</dead-letter-queue-suffix>
</address-setting>

可以通过直接使用队列的名称 (例如,当使用核心客户端时) 或使用完全限定的队列名称 (例如,当使用 JMS 客户端时) 来访问保存不可送达消息的队列,就像访问任何其他队列一样。此外,请注意,该队列是自动创建的,这意味着它将根据相关的 address-settings 自动删除。

2.4. 例子

请参阅:示例 中的死信部分,其中显示了如何使用 JMS 静态配置和使用死信资源。

3. 送达次数持久化

在正常使用情况下,Apache ActiveMQ Artemis 不会持久化更新送达次数,直到消息回滚 (即,在消息送达消费者之前不会更新送达次数)。在大多数消息传递用例中,消息在消费后会立即被消费、确认并忘记。在这些情况下,在送达消息之前持久化更新送达次数将为每个送达的消息增加一个额外的持久化步骤,这意味着性能将显著下降。

但是,如果在消息送达之前没有持久化更新送达次数,那么在服务器崩溃的情况下,消息可能已被送达,但这不会反映在送达次数中。在恢复阶段,服务器将不知道这一点,并将使用 redelivered 设置为 false 送达消息,而它应该是 true

由于此行为违反了严格的 JMS 语义,因此 Apache ActiveMQ Artemis 允许在消息送达之前持久化送达次数,但此功能默认情况下处于禁用状态,因为会影响性能。

要启用它,请在 broker.xml 中将 persist-delivery-count-before-delivery 设置为 true

<persist-delivery-count-before-delivery>true</persist-delivery-count-before-delivery>