Dead Letter Channel - 6.3

Talend ESB Mediation Developer Guide

EnrichVersion
6.3
EnrichProdName
Talend Data Fabric
Talend Data Services Platform
Talend ESB
Talend MDM Platform
Talend Open Studio for ESB
Talend Real-Time Big Data Platform
task
Design and Development
EnrichPlatform
Talend ESB

Camel supports the Dead Letter Channel from the EIP patterns using the DeadLetterChannel processor which is an Error Handler. For more information about the DeadLetterChannel processor, refer to its corresponding Camel-core API documentation on http://camel.apache.org/.

Note

The major difference between Dead Letter Channel and the Default Error Handler is that Dead Letter Channel has a dead letter queue that whenever an Exchange could not be processed is moved to. It will always move failed exchanges to this queue.

Unlike the Default Error Handler that does not have a dead letter queue. So whenever an Exchange could not be processed the error is propagated back to the client.

Notice: You can adjust this behavior of whether the client should be notified or not with the handled option.

Redelivery

It is common for a temporary outage or database deadlock to cause a message to fail to process; but the chances are if it is 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

  • how many times a message is attempted to be redelivered before it is considered a failure and sent to the dead letter channel

  • the initial redelivery timeout

  • whether or not exponential backoff is used (i.e. the time between retries increases using a backoff multiplier)

  • whether to use collision avoidance to add some randomness to the timings

  • delay pattern, see below for details.

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 handled

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. With this process the Dead Letter Channel has handled the Exchange.

For instance configuring the dead letter channel, using the fluent builders:

errorHandler(deadLetterChannel("jms:queue:dead")
    .maximumRedeliveries(3).redeliverDelay(5000));

Using 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 message

The 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 useOriginalBody 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.

OnRedelivery

When Dead Letter Channel is doing redelivery it is 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 it is redelivered. See below for sample.

We also support for per onException to set a onRedeliver. That means you can do special on redelivery for different exceptions, as opposed to onRedelivery set on Dead Letter Channel can be viewed as a global scope.

Redelivery default values

Redelivery is disabled by default. The default redelivery policy uses the following values:

  • maximumRedeliveries=0

  • redeliverDelay=1000L (1 second)

    • use initialRedeliveryDelay for previous versions

  • maximumRedeliveryDelay = 60 * 1000L (60 seconds)

  • And the exponential backoff and collision avoidance is turned off.

  • The retriesExhaustedLogLevel are set to LoggingLevel.ERROR

  • The retryAttemptedLogLevel are set to LoggingLevel.DEBUG

  • Stack traces is logged for exhausted messages.

  • Handled exceptions is not logged

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. Setting the maximumRedeliveries to a negative value such as -1 will then always redelivery (unlimited). Setting the maximumRedeliveries to 0 will disable any re delivery attempt.

Camel will log delivery failures at the DEBUG logging level by default. You can change this by specifying retriesExhaustedLogLevel and/or retryAttemptedLogLevel.

You can turn logging of stack traces on/off. If turned off Camel will still log the redelivery attempt; but it's much less verbose.

Redeliver Delay Pattern

Delay pattern is used as a single option to set a range pattern for delays. If used then the following options do 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

  • limit = upper limit

  • delay = delay in milliseconds

    And the groups is again separated with semi colon.

    The rule of thumb is that the next groups should have a higher limit than the previous group.

Let's clarify this with an example: delayPattern=5:1000;10:5000;20:20000

That gives us 3 groups:

  • 5:1000

  • 10:5000

  • 20:20000

Resulting in these delays for redelivery attempt:

  • Redelivery attempt number 1..4 = 0 ms (as the first group start with 5)

  • Redelivery attempt number 5..9 = 1000 ms (the first group)

  • Redelivery attempt number 10..19 = 5000 ms (the second group)

  • Redelivery attempt number 20.. = 20000 ms (the last group)

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

  • Redelivery attempt number 1..4 = 1000 ms (the first group)

  • Redelivery attempt number 5.. = 5000 ms (the last group)

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 header

When a message is redelivered the DeadLetterChannel will append a customizable header to the message to indicate how many times it has been redelivered. The header CamelRedeliveryMaxCounter, which is also defined on the Exchange.REDELIVERY_MAX_COUNTER, contains the maximum redelivery setting. This header is absent if you use retryWhile or have unlimited maximum redelivery configured.

And a boolean flag whether it is being redelivered or not (first attempt). The header CamelRedelivered contains a boolean if the message is redelivered or not, which is also defined on the Exchange.REDELIVERED.

There's an additional header, CamelRedeliveryDelay, to show any dynamically calculated delay from the exchange. This is also defined on the Exchange.REDELIVERY_DELAY. If this header is absent, normal redelivery rules will apply.

Which endpoint failed

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 it's 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. This is useable if the Camel route is a bit dynamic such as the dynamic Recipient List so you know which endpoints failed.

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 Camel Component: Bean processing instead. So be aware 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 .

Starting with Camel 2.11, the route that failed can also be determined by using the following:

String failedRouteId = exchange.getProperty(Exchange.FAILURE_ROUTE_ID, 
   String.class);

Which route failed

Available 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 the Exchange with the route id where the error occurred.

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.

Samples

The 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 uses at most 2 redeliveries
        // and exponential backoff
        errorHandler(deadLetterChannel("seda:errors").
            maximumRedeliveries(2).useExponentialBackOff());

        // here is our route
        from("seda:a").to("seda:b");
    }
};