6.3. 死信频道
概述
图 6.3 “死信频道模式” 中显示的 死信频道 模式 描述了当消息传递系统无法向预期接收方发送消息时要执行的操作。这包括重试发送等功能,如果发送最终失败,将消息发送到死信频道,该频道存档了未发送的消息。
图 6.3. 死信频道模式
在 Java DSL 中创建死信频道
以下示例演示了如何使用 Java DSL 创建死信频道:
errorHandler(deadLetterChannel("seda:errors")); from("seda:a").to("seda:b");
其中 errorHandler ()
方法是一个 Java DSL 拦截器,这意味着当前路由构建器中定义的所有路由都受到此设置的影响。deadLetterChannel ()
方法是一个 Java DSL 命令,它会创建一个带有指定目的地端点 seda:errors
的新死信频道。
errorHandler ()
拦截器提供处理所有错误类型的 catch- all 机制。如果要应用更精细的方法来异常处理,您可以使用 onException
子句(请参阅 “onException 子句”一节)。
XML DSL 示例
您可以在 XML DSL 中定义死信频道,如下所示:
<route errorHandlerRef="myDeadLetterErrorHandler"> ... </route> <bean id="myDeadLetterErrorHandler" class="org.apache.camel.builder.DeadLetterChannelBuilder"> <property name="deadLetterUri" value="jms:queue:dead"/> <property name="redeliveryPolicy" ref="myRedeliveryPolicyConfig"/> </bean> <bean id="myRedeliveryPolicyConfig" class="org.apache.camel.processor.RedeliveryPolicy"> <property name="maximumRedeliveries" value="3"/> <property name="redeliveryDelay" value="5000"/> </bean>
重新发送策略
通常,如果发送尝试失败,您不会将邮件直接发送到死信频道。相反,您可以重新尝试发送到某些最大限制,在重新发送尝试失败后会将消息发送到死信频道。要自定义消息重新发送,您可以将死信频道配置为具有 重新传送策略。例如,要指定最多两个重新发送尝试,并将 exponential backoff 算法应用到交付尝试之间的时间延迟,您可以配置死信频道,如下所示:
errorHandler(deadLetterChannel("seda:errors").maximumRedeliveries(2).useExponentialBackOff()); from("seda:a").to("seda:b");
您可以通过调用链中的相关方法(链中的每个方法返回对当前 RedeliveryPolicy
对象的引用),在死信频道上设置重新传送选项。表 6.1 “重新发送策略设置” 总结了可用于设置重新发送策略的方法。
方法签名 | default | 描述 |
---|---|---|
|
| 控制在安全关闭期间或路由停止期间是否尝试重新发送。在启动停止时已正在进行的交付不会中断。 |
|
|
如果启用了 exponential backoff,请让 d, m*d, m*m*d, m*m*m*d, ... |
|
|
如果启用了冲突避免,让其 |
|
|
Camel 2.15:指定是否在处理死信通道中的消息时出现的异常。如果为 |
| None | Apache Camel 2.0: 请参阅 “redeliver 延迟模式”一节。 |
|
|
Apache Camel 2.0:禁用重新发送功能。要启用重新发送,请将 |
|
|
Apache Camel 2.0: 如果为 |
|
| 指定尝试第一次重新发送前的延迟(以毫秒为单位)。 |
|
| 指定是否在 dead letter 频道中引发异常时,是否登录到 WARN 级别。 |
|
|
Apache Camel 2.0:如果为 |
|
| Apache Camel 2.0:交付尝试的最大数量。 |
|
|
Apache Camel 2.0:在使用 exponential backoff 策略时(请参阅 |
| None | Apache Camel 2.0:配置在每次重新发送尝试前调用的处理器。 |
|
| Apache Camel 2.0:指定重新发送尝试之间的延迟(以毫秒为单位)。Apache Camel 2.16.0 :默认重新发送延迟为一秒。 |
|
|
Apache Camel 2.0:指定记录交付失败的日志记录级别(指定为 |
|
|
Apache Camel 2.0:指定重新发送尝试的日志级别(指定为 |
|
| 启用冲突避免问题,这会为 backoff 时间添加一些随机化,以减少竞争的可能性。 |
|
|
Apache Camel 2.0:如果启用了此功能,则发送到死信频道的消息是 原始消息 交换的副本,因为它存在于路由开始时( |
|
| 启用 exponential backoff。 |
重新发送标头
如果 Apache Camel 尝试重新设计一个信息,它会自动设置 In 消息中 表 6.2 “死信重新发送标头” 中描述的标头。
标头名称 | 类型 | 描述 |
---|---|---|
|
|
Apache Camel 2.0:计算发送尝试失败次数。此值也在 |
|
|
Apache Camel 2.0: True,如果进行了一个或多个重新发送尝试。此值也在 |
|
|
Apache Camel 2.6:保存最大重新发送设置(也在 |
重新发送交换属性
如果 Apache Camel 尝试恢复消息,它会自动设置 表 6.3 “重新发送交换属性” 中描述的交换属性。
Exchange Property 名称 | 类型 | 描述 |
---|---|---|
|
|
提供失败的路由的路由 ID。此属性的字面名称为 |
使用原始消息
从 Apache Camel 2.0 开始,因为 交换对象因通过路由而受到修改,所以当引发异常并不一定要存储在死信频道中的副本时,当前使用的交换对象不一定要存储在死信频道中。在很多情况下,最好在路由开始前记录消息,然后再受到路由的任何转换。例如,请考虑以下路由:
from("jms:queue:order:input") .to("bean:validateOrder"); .to("bean:transformOrder") .to("bean:handleOrder");
前面的路由侦听传入的 JMS 消息,然后使用 Bean 序列处理消息: validateOrder
、transformOrder
和 handleOrder
。但当发生错误时,我们不知道消息所处的状态。在 transformOrder
bean 之前或之后是否发生错误?我们可以通过启用 useOriginalMessage
选项来确保来自 jms:queue:order:input
的原始消息记录到 dead letter 频道中,如下所示:
// will use original body errorHandler(deadLetterChannel("jms:queue:dead") .useOriginalMessage().maximumRedeliveries(5).redeliveryDelay(5000);
redeliver 延迟模式
作为 Apache Camel 2.0 提供的,delayPattern
选项用于指定重新发送计数的特定范围的延迟。延迟模式具有以下语法: limit1:delay1;limit2:delay2;limit3:delay3;…
,其中每个 delayN 应用到范围limitN urllib redeliveryCount < limitN+1
例如,考虑模式 5:1000;10:5000;20:20000
,它定义了三个组,并产生以下重新发送延迟:
- 尝试编号 1..4 = 0 毫秒(当第一个组以 5 开始)。
- 尝试编号 5..9 = 1000 毫秒(第一个组)。
- 尝试编号 10.19 = 5000 毫秒(第二个组)。
- 尝试编号 20.. = 20000 毫秒(最后一个组)。
您可以使用限制 1 启动组来定义启动延迟。例如,1:1000;5:5000
会产生以下重新发送延迟:
- 尝试编号 1..4 = 1000 millis (第一个组)
- Try number 5.. = 5000 millis (最后一个组)
不需要下一个延迟应高于上一个延迟,您可以使用您喜欢的任何延迟值。例如,延迟模式 1:5000;3:1000
,以 5 秒延迟开头,然后将延迟降低为 1 秒。
哪一个端点失败?
当 Apache Camel 路由消息时,它会更新一个 Exchange 发送到 的最后一个 端点的 Exchange 属性。因此,您可以使用以下代码获取当前交换的最新目的地的 URI:
// Java String lastEndpointUri = exchange.getProperty(Exchange.TO_ENDPOINT, String.class);
其中 Exchange.TO_ENDPOINT
是字符串常量等于 CamelToEndpoint
。每当 Camel 向任何端点发送消息时,都会更新此属性。
如果在路由期间发生错误,并且交换移到死信队列中,Apache Camel 还将设置名为 CamelFailureEndpoint
的属性,其标识交换在发生错误之前发送到的最后一个目标。因此,您可以使用以下代码从死信队列中访问失败端点:
// Java String failedEndpointUri = exchange.getProperty(Exchange.FAILURE_ENDPOINT, String.class);
其中 Exchange.FAILURE_ENDPOINT
是字符串常量等于 CamelFailureEndpoint
。
这些属性保留在当前交换中,即使给定目标端点完成处理后也发生了故障。例如,请考虑以下路由:
from("activemq:queue:foo") .to("http://someserver/somepath") .beanRef("foo");
现在假设 foo
bean 中出现失败。在这种情况下,Exchange.TO_ENDPOINT
属性和 Exchange.FAILURE_ENDPOINT
属性仍然包含该值。
onRedelivery 处理器
当死信频道正在执行红色时,可以配置每次 重新发送
尝试 前 仅执行的处理器。这可用于需要修改消息的情况,然后再重新设计消息。
例如,以下死信频道被配置为在 redelivering Exchanges 前调用 MyRedeliverProcessor
:
// we configure our Dead Letter Channel to invoke // MyRedeliveryProcessor before a redelivery is // attempted. This allows us to alter the message before errorHandler(deadLetterChannel("mock:error").maximumRedeliveries(5) .onRedelivery(new MyRedeliverProcessor()) // setting delay to zero is just to make unit teting faster .redeliveryDelay(0L));
其中 MyRedeliveryProcessor
进程实现,如下所示:
// This is our processor that is executed before every redelivery attempt
// here we can do what we want in the java code, such as altering the message
public class MyRedeliverProcessor implements Processor {
public void process(Exchange exchange) throws Exception {
// the message is being redelivered so we can alter it
// we just append the redelivery counter to the body
// you can of course do all kind of stuff instead
String body = exchange.getIn().getBody(String.class);
int count = exchange.getIn().getHeader(Exchange.REDELIVERY_COUNTER, Integer.class);
exchange.getIn().setBody(body + count);
// the maximum redelivery was set to 5
int max = exchange.getIn().getHeader(Exchange.REDELIVERY_MAX_COUNTER, Integer.class);
assertEquals(5, max);
}
}
控制在关闭或停止期间重新发送
如果您停止路由或启动安全关闭,错误处理程序的默认行为是继续尝试重新发送。因为这通常不是所需的行为,因此您可以选择在关闭或停止期间禁用重新发送,方法是将 allowRedeliveryWhileStopping
选项设置为 false
,如下例所示:
errorHandler(deadLetterChannel("jms:queue:dead")
.allowRedeliveryWhileStopping(false)
.maximumRedeliveries(20)
.redeliveryDelay(1000)
.retryAttemptedLogLevel(LoggingLevel.INFO));
allowRedeliveryWhileStopping
选项默认为 true
,因为向后兼容的原因。但是,在积极关闭期间,始终禁止重新发送,无论此选项设置都无关(例如,在安全关闭超时后)。
使用 onExceptionOccurred Processor
dead Letter 频道支持 onExceptionOccurred 处理器,允许在发生异常后对消息进行自定义处理。您也可以使用它进行自定义日志记录。来自 onExceptionOccurred 处理器抛出的任何新异常都记录为 WARN,并忽略,而不是覆盖现有的异常。
onRedelivery 处理器和 onExceptionOccurred 处理器之间的区别是,您可以在重新发送尝试前完全处理前的前者。但是,发生异常后不会立即发生。例如,如果您将错误处理程序配置为在重新发送尝试之间进行五秒延迟,则稍后会调用重新传送处理器 5 秒,之后异常会调用 5 秒。
以下示例解释了如何在发生异常时执行自定义日志记录。您需要配置 onExceptionOccurred 以使用自定义处理器。
errorHandler(defaultErrorHandler().maximumRedeliveries(3).redeliveryDelay(5000).onExceptionOccurred(myProcessor));
onException 子句
您可以在路由构建器中使用 errorHandler ()
拦截器,而是定义一系列 onException ()
子句,来为各种异常类型定义不同的重新传送策略和不同的死信频道。例如,要为每个 NullPointerException
、IOException
和 Exception
类型定义不同的行为,您可以使用 Java DSL 在路由构建器中定义以下规则:
onException(NullPointerException.class) .maximumRedeliveries(1) .setHeader("messageInfo", "Oh dear! An NPE.") .to("mock:npe_error"); onException(IOException.class) .initialRedeliveryDelay(5000L) .maximumRedeliveries(3) .backOffMultiplier(1.0) .useExponentialBackOff() .setHeader("messageInfo", "Oh dear! Some kind of I/O exception.") .to("mock:io_error"); onException(Exception.class) .initialRedeliveryDelay(1000L) .maximumRedeliveries(2) .setHeader("messageInfo", "Oh dear! An exception.") .to("mock:error"); from("seda:a").to("seda:b");
其中,通过串联重新传送策略方法(如 表 6.1 “重新发送策略设置”中列出的)来指定重新传送选项,您可以使用 to ()
DSL 命令指定死信频道的端点。您还可以在 onException ()
子句中调用其他 Java DSL 命令。例如,上例调用 setHeader ()
,在名为 messageInfo
的消息标头中记录一些错误详情。
在本例中,NullPointerException
和 IOException
异常类型被特殊配置。所有其他例外类型都由通用例外 例外
拦截器处理。默认情况下,Apache Camel 应用最符合所给异常的异常拦截器。如果无法找到完全匹配,它会尝试匹配最接近的基本类型,以此类推。最后,如果没有其他拦截器匹配,则 Exception
类型的拦截器与所有剩余的例外匹配。
OnPrepareFailure
在将交换传递给死信队列之前,您可以使用 onPrepare
选项来允许自定义处理器准备交换。它允许您添加有关交换的信息,如交换失败的原因。例如,以下处理器添加一个带有异常消息的标头。
public class MyPrepareProcessor implements Processor { @Override public void process(Exchange exchange) throws Exception { Exception cause = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class); exchange.getIn().setHeader("FailedBecause", cause.getMessage()); } }
您可以将错误处理程序配置为使用处理器,如下所示。
errorHandler(deadLetterChannel("jms:dead").onPrepareFailure(new MyPrepareProcessor()));
但是,也可以使用默认错误处理程序使用 onPrepare
选项。
<bean id="myPrepare" class="org.apache.camel.processor.DeadLetterChannelOnPrepareTest.MyPrepareProcessor"/> <errorHandler id="dlc" type="DeadLetterChannel" deadLetterUri="jms:dead" onPrepareFailureRef="myPrepare"/>