Dead Letter ChannelCamel supports the Dead Letter Channel from the EIP patterns using the DeadLetterChannel processor which is an Error Handler.
RedeliveryIt is common for a temporary outage or database deadlock to cause a message to fail to process; but the chances are if its tried a few more times with some time delay then it will complete fine. So we typically wish to use some kind of redelivery policy to decide how many times to try redeliver a message and how long to wait before redelivery attempts. The RedeliveryPolicy defines how the message is to be redelivered. You can customize things like
Once all attempts at redelivering the message fails then the message is forwarded to the dead letter queue. About moving Exchange to dead letter queue and using handledHandled on Dead Letter Channel When all attempts of redelivery have failed the Exchange is moved to the dead letter queue (the dead letter endpoint). The exchange is then complete and from the client point of view it was processed. As such the Dead Letter Channel have handled the Exchange. For instance configuring the dead letter channel as: Using the Fluent Builders
errorHandler(deadLetterChannel("jms:queue:dead")
.maximumRedeliveries(3).redeliveryDelay(5000));
Using the Spring XML Extensions <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> The Dead Letter Channel above will clear the caused exception (setException(null)), by moving the caused exception to a property on the Exchange, with the key Exchange.EXCEPTION_CAUGHT. Then the Exchange is moved to the "jms:queue:dead" destination and the client will not notice the failure. About moving Exchange to dead letter queue and using the original messageThe option useOriginalMessage is used for routing the original input message instead of the current message that potentially is modified during routing. For instance if you have this route: from("jms:queue:order:input") .to("bean:validateOrder") .to("bean:transformOrder") .to("bean:handleOrder"); The route listen for JMS messages and validates, transforms and handle it. During this the Exchange payload is transformed/modified. So in case something goes wrong and we want to move the message to another JMS destination, then we can configure our Dead Letter Channel with the useOriginalMessage option. But when we move the Exchange to this destination we do not know in which state the message is in. Did the error happen in before the transformOrder or after? So to be sure we want to move the original input message we received from jms:queue:order:input. So we can do this by enabling the useOriginalMessage option as shown below:
// will use original body
errorHandler(deadLetterChannel("jms:queue:dead")
.useOriginalMessage().mamimumRedeliveries(5).redeliverDelay(5000);
Then the messages routed to the jms:queue:dead is the original input. If we want to manually retry we can move the JMS message from the failed to the input queue, with no problem as the message is the same as the original we received. OnRedeliveryWhen Dead Letter Channel is doing redeliver its possible to configure a Processor that is executed just before every redelivery attempt. This can be used for the situations where you need to alter the message before its redelivered. See below for sample.
Redelivery default valuesRedelivery is disabled by default. The default redeliver policy will use the following values:
The maximum redeliver delay ensures that a delay is never longer than the value, default 1 minute. This can happen if you turn on the exponential backoff. The maximum redeliveries is the number of re delivery attempts. By default Camel will try to process the exchange 1 + 5 times. 1 time for the normal attempt and then 5 attempts as redeliveries. Camel will log delivery failures at the DEBUG logging level by default. You can change this by specifying retriesExhaustedLogLevel and/or retryAttemptedLogLevel. See ExceptionBuilderWithRetryLoggingLevelSetTest for an example. You can turn logging of stack traces on/off. If turned off Camel will still log the redelivery attempt. Its just much less verbose. Redeliver Delay PatternDelay pattern is used as a single option to set a range pattern for delays. If used then the following options does not apply: (delay, backOffMultiplier, useExponentialBackOff, useCollisionAvoidance, maximumRedeliveryDelay). The idea is to set groups of ranges using the following syntax: limit:delay;limit 2:delay 2;limit 3:delay 3;...;limit N:delay N Each group has two values separated with colon
Lets clarify this with an example: That gives us 3 groups:
Resulting in these delays for redelivery attempt:
Note: The first redelivery attempt is 1, so the first group should start with 1 or higher. You can start a group with limit 1 to eg have a starting delay: delayPattern=1:1000;5:5000
There is no requirement that the next delay should be higher than the previous. You can use any delay value you like. For example with delayPattern=1:5000;3:1000 we start with 5 sec delay and then later reduce that to 1 second. Redelivery headerWhen a message is redelivered the DeadLetterChannel will append a customizable header to the message to indicate how many times its been redelivered. And a boolean flag whether it is being redelivered or not (first attempt) Dynamically calculated delay from the exchange Which endpoint failedAvailable as of Camel 2.1 When Camel routes messages it will decorate the Exchange with a property that contains the last endpoint Camel send the Exchange to: String lastEndpointUri = exchange.getProperty(Exchange.TO_ENDPOINT, String.class); The Exchange.TO_ENDPOINT have the constant value CamelToEndpoint. This information is updated when Camel sends a message to any endpoint. So if it exists its the last endpoint which Camel send the Exchange to. When for example processing the Exchange at a given Endpoint and the message is to be moved into the dead letter queue, then Camel also decorates the Exchange with another property that contains that last endpoint: String failedEndpointUri = exchange.getProperty(Exchange.FAILURE_ENDPOINT, String.class); The Exchange.FAILURE_ENDPOINT have the constant value CamelFailureEndpoint. This allows for example you to fetch this information in your dead letter queue and use that for error reporting. Notice: These information is kept on the Exchange even if the message was successfully processed by a given endpoint, and then later fails for example in a local Bean processing instead. So beware that this is a hint that helps pinpoint errors. from("activemq:queue:foo") .to("http://someserver/somepath") .beanRef("foo"); Now suppose the route above and a failure happens in the foo bean. Then the Exchange.TO_ENDPOINT and Exchange.FAILURE_ENDPOINT will still contain the value of http://someserver/somepath. Which route failedAvailable as of Camel 2.10.4/2.11 When Camel error handler handles an error such as Dead Letter Channel or using Exception Clause with handled=true, then Camel will decorate String failedRouteId = exchange.getProperty(Exchange.FAILURE_ROUTE_ID, String.class); The Exchange.FAILURE_ROUTE_ID have the constant value CamelFailureRouteId. This allows for example you to fetch this information in your dead letter queue and use that for error reporting. Control if redelivery is allowed during stopping/shutdownAvailable as of Camel 2.11 Prior to Camel 2.10, Camel will perform redelivery while stopping a route, or shutting down Camel. This has improved a bit in Camel 2.10 onwards, as Camel will not perform redelivery attempts when shutting down aggressively (eg during Graceful Shutdown and timeout hit). From Camel 2.11 onwards there is a new option allowRedeliveryWhileStopping which you can use to control if redelivery is allowed or not; notice that any in progress redelivery will still be executed. This option can only disallow any redelivery to be executed after the stopping of a route/shutdown of Camel has been triggered. If a redelivery is dissallowed then a RejectedExcutionException is set on the Exchange and the processing of the Exchange stops. This means any consumer will see the Exchange as failed due the RejectedExecutionException. The default value is true to be backwards compatible as before. For example the following sample shows how to do this with Java DSL and XML DSL // this error handler will try up till 20 redelivery attempts with 1 second between. // however if we are stopping then do not allow any redeliver attempts. errorHandler(defaultErrorHandler() .allowRedeliveryWhileStopping(false) .maximumRedeliveries(20).redeliveryDelay(1000).retryAttemptedLogLevel(LoggingLevel.INFO)); from("seda:foo").routeId("foo") .to("mock:foo") .throwException(new IllegalArgumentException("Forced")); And the sample sample with XML DSL <!-- notice we use the errorHandlerRef attribute to refer to the error handler to use as default --> <camelContext errorHandlerRef="myErrorHandler" xmlns="http://camel.apache.org/schema/spring"> <!-- configure error handler, to redeliver up till 10 times, with 1 sec delay and if we are stopping then do not allow redeliveries, to stop faster --> <errorHandler id="myErrorHandler" type="DefaultErrorHandler"> <redeliveryPolicy maximumRedeliveries="20" redeliveryDelay="1000" allowRedeliveryWhileStopping="false" retryAttemptedLogLevel="INFO"/> </errorHandler> <route id="foo"> <from uri="seda:foo"/> <to uri="mock:foo"/> <throwException ref="forced"/> </route> </camelContext> SamplesThe following example shows how to configure the Dead Letter Channel configuration using the DSL RouteBuilder builder = new RouteBuilder() { public void configure() { // using dead letter channel with a seda queue for errors errorHandler(deadLetterChannel("seda:errors")); // here is our route from("seda:a").to("seda:b"); } }; You can also configure the RedeliveryPolicy as this example shows RouteBuilder builder = new RouteBuilder() { public void configure() { // configures dead letter channel to use seda queue for errors and use at most 2 redelveries // and exponential backoff errorHandler(deadLetterChannel("seda:errors").maximumRedeliveries(2).useExponentialBackOff()); // here is our route from("seda:a").to("seda:b"); } }; How can I modify the Exchange before redelivery?We support directly in Dead Letter Channel to set a Processor that is executed before each redelivery attempt. When Dead Letter Channel is doing redeliver its possible to configure a Processor that is executed just before every redelivery attempt. This can be used for the situations where you need to alter the message before its redelivered. Here we configure the Dead Letter Channel to use our processor MyRedeliveryProcessor to be executed before each redelivery. // 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 testing faster .redeliveryDelay(0L)); And this is the processor MyRedeliveryProcessor where we alter the message. // 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); } } Using This PatternIf you would like to use this EIP Pattern then please read the Getting Started, you may also find the Architecture useful particularly the description of Endpoint and URIs. Then you could try out some of the Examples first before trying this pattern out. |
