6.3. dead Letter Channel

6.3.1. 개요

그림 6.3. “dead letter 채널 패턴” 에 표시된 dead letter 채널 패턴은 메시징 시스템이 의도한 수신자에 메시지를 전달하지 못하는 경우 수행할 작업을 설명합니다. 여기에는 전달 재시도와 같은 기능이 포함되며, 전달이 궁극적으로 실패하는 경우 전달되지 않은 메시지를 보관하는 dead letter 채널로 메시지를 보냅니다.

그림 6.3. dead letter 채널 패턴

dead letter channel pattern

6.3.2. Java DSL에서 dead letter 채널 생성

다음 예제에서는 Java DSL을 사용하여 dead letter 채널을 생성하는 방법을 보여줍니다.

errorHandler(deadLetterChannel("seda:errors"));
from("seda:a").to("seda:b");

errorHandler() 메서드는 Java DSL 인터셉터이며, 이는 현재 경로 빌더에 정의된 모든 경로가 이 설정의 영향을 받는 것을 의미합니다. deadLetterChannel() 메서드는 지정된 대상 끝점인 seda:errors 를 사용하여 새 dead letter 채널을 생성하는 Java DSL 명령입니다.

errorHandler() 인터셉터는 모든 오류 유형을 처리하기 위한 catch- all 메커니즘을 제공합니다. 예외 처리에 보다 세분화된 접근 방식을 적용하려면 onException 절을 대신 사용할 수 있습니다( “onException 절”참조).

6.3.3. XML DSL 예

다음과 같이 XML DSL에서 dead letter 채널을 정의할 수 있습니다.

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

6.3.4. Redelivery policy

일반적으로 전달 시도가 실패하는 경우 dead letter 채널에 직접 메시지를 보내지 않습니다. 대신 최대 제한까지 전송을 다시 시도하면 모든 재전송 시도가 실패하면 메시지를 dead letter 채널로 보냅니다. 메시지 재전송을 사용자 정의하기 위해 dead letter 채널을 구성하여 재전송 정책을 설정할 수 있습니다. 예를 들어 최대 두 개의 재전송 시도를 지정하고 지수 백오프 알고리즘을 전달 시도 사이의 시간 지연에 적용하려면 다음과 같이 dead letter 채널을 구성할 수 있습니다.

errorHandler(deadLetterChannel("seda:errors").maximumRedeliveries(2).useExponentialBackOff());
from("seda:a").to("seda:b");

체인에서 관련 메서드를 호출하여 dead letter 채널에 redelivery 옵션을 설정하는 경우 체인의 각 메서드는 현재 RedeliveryPolicy 오브젝트에 대한 참조를 반환합니다. 표 6.1. “Redelivery Policy Settings” 재전송 정책을 설정하는 데 사용할 수 있는 방법이 요약되어 있습니다.

표 6.1. Redelivery Policy Settings

메서드 서명Default설명

allowRedeliveryWhileStopping()

true

정상 종료 중에 또는 경로가 중지되는 동안 재전송이 시도되는지 여부를 제어합니다. 중지가 시작될 때 이미 진행 중인 제공은 중단 되지 않습니다.

backOffMultiplier(double multiplier)

2

exponential 백오프가 활성화된 경우 m 을 백오프 multiplier로 설정하고 d 를 초기 지연으로 설정합니다. 그런 다음 재전송 시도 시퀀스가 다음과 같이 시간 초과됩니다.

d, m*d, m*m*d, m*m*m*d, ...

collisionAvoidancePercent(double collisionAvoidancePercent)

15

충돌 방지 기능이 활성화된 경우 p 를 충돌 방지 백분율로 설정합니다. 그런 다음 충돌 방지 정책은 다음 지연을 현재 값의 plus/minus p% 까지 임의의 양만큼 조정합니다.

deadLetterHandleNewException

true

Camel 2.15: dead letter 채널에서 메시지를 처리하는 동안 발생하는 예외를 처리할지 여부를 지정합니다. true 인 경우 예외가 처리되고 WARN 수준에서 로깅되어 dead letter 채널이 완료되도록 합니다. false 인 경우 예외가 처리되지 않으므로 dead letter 채널이 실패하고 새 예외를 전파합니다.

delayPattern(String delayPattern)

없음

Apache Camel 2.0: “지연 패턴 재전송” 참조.

disableRedelivery()

true

Apache Camel 2.0: 재전송 기능을 비활성화합니다. 재전송을 활성화하려면 maximumRedeliveries() 를 양의 정수 값으로 설정합니다.

처리(부울 처리)

true

Apache Camel 2.0: true 인 경우 메시지가 dead letter 채널로 이동될 때 현재 예외가 지워집니다. false 인 경우 예외가 클라이언트에 다시 전파됩니다.

initialRedeliveryDelay(long initialRedeliveryDelay)

1000

첫 번째 재전송을 시도하기 전에 지연 (밀리초)을 지정합니다.

logNewException

true

dead letter 채널에서 예외가 발생하는 경우 WARN 수준에서 로그할지 여부를 지정합니다.

logStackTrace(boolean logStackTrace)

false

Apache Camel 2.0: true 인 경우 JVM 스택 추적이 오류 로그에 포함됩니다.

maximumRedeliveries(int maximumRedeliveries)

0

Apache Camel 2.0: 제공 시도의 최대 수입니다.

maximumRedeliveryDelay(long maxDelay)

60000

Apache Camel 2.0: 지수 백오프 전략을 사용하는 경우( ExponentialBackOff()참조)는 이론적으로 제한 없이 재전송 지연을 늘릴 수 있습니다. 이 속성은 재전송 지연(밀리초)에 상한 제한을 적용합니다.

onRedelivery(프로세서 프로세서)

없음

Apache Camel 2.0: 모든 재전송 시도 전에 호출되는 프로세서를 구성합니다.

redeliveryDelay(long int)

0

Apache Camel 2.0: 재전송 시도 사이의 지연(밀리초)을 지정합니다. Apache Camel 2.16.0: 기본 재전송 지연은 1초입니다.

retriesExhaustedLogLevel(LoggingLevel logLevel)

LoggingLevel.ERROR

Apache Camel 2.0: 전송 실패를 기록할 로깅 수준을 지정합니다( org.apache.camel.LoggingLevel 상수로 지정됨).

retryAttemptedLogLevel(LoggingLevel logLevel)

LoggingLevel.DEBUG

Apache Camel 2.0: 시도를 다시 전달할 로깅 수준을 지정합니다( org.apache.camel.LoggingLevel 상수로 지정됨).

useCollisionAvoidance()

false

경합 가능성을 줄이기 위해 백오프 타이밍에 일부 임의화를 추가하는 충돌 방지를 활성화합니다.

useOriginalMessage()

false

Apache Camel 2.0: 이 기능이 활성화되면 dead letter 채널로 전송된 메시지는 경로 시작 부분에 존재하기 때문에 원래 메시지 교환의 사본입니다( from() 노드).

useExponentialBackOff()

false

지수 백오프를 활성화합니다.

6.3.5. 헤더 재전송

Apache Camel이 메시지를 다시 전달하려고 하면 In 메시지의 표 6.2. “dead letter Redelivery Headers” 에 설명된 헤더가 자동으로 설정됩니다.

표 6.2. dead letter Redelivery Headers

헤더 이름유형설명

CamelRedeliveryCounter

정수

Apache Camel 2.0: 실패한 제공 시도 횟수를 계산합니다. 이 값은 Exchange.REDELIVERY_COUNTER 에서도 설정됩니다.

CamelRedelivered

부울

Apache Camel 2.0: 하나 이상의 재전송 시도가 이루어진 경우 True입니다. 이 값은 Exchange.REDELIVERED 에서도 설정됩니다.

CamelRedeliveryMaxCounter

정수

Apache Camel 2.6: 최대 재전송 설정을 유지합니다( Exchange.REDELIVERY_MAX_COUNTER 교환 속성에 설정됨). retryWhile 를 사용하거나 무제한 최대 재전송이 구성된 경우 이 헤더가 없습니다.

6.3.6. Redelivery exchange properties

Apache Camel이 메시지를 다시 전달하려고 하면 표 6.3. “교환 속성 재전송” 에 설명된 교환 속성이 자동으로 설정됩니다.

표 6.3. 교환 속성 재전송

자산 이름 교환유형설명

Exchange.FAILURE_ROUTE_ID

문자열

실패한 경로의 경로 ID를 제공합니다. 이 속성의 리터럴 이름은 CamelFailureRouteId 입니다.

6.3.7. 원본 메시지 사용

Apache Camel 2.0부터 사용할 수 있습니다. 교환 오브젝트는 경로를 통해 전달될 때 수정될 수 있으므로 예외가 발생한 경우 현재 교환이 dead letter 채널에 저장하려는 사본이 아닙니다. 대부분의 경우 경로에서 모든 종류의 변환이 적용되기 전에 경로 시작 부분에 도착한 메시지를 기록하는 것이 좋습니다. 예를 들어 다음 경로를 고려하십시오.

from("jms:queue:order:input")
       .to("bean:validateOrder");
       .to("bean:transformOrder")
       .to("bean:handleOrder");

이전 경로는 들어오는 JMS 메시지를 수신 대기한 다음, beans 시퀀스를 사용하여 메시지를 처리합니다. validateOrder,transformOrder, handleOrder. 그러나 오류가 발생하면 메시지가 어떤 상태에 있는지 알 수 없습니다. transformOrder Cryostat 또는 이후 버전 이전에 오류가 발생했습니까? 다음과 같이 original message from jms:queue:order:input 가 dead letter channel에 기록되도록 수 있습니다.

// will use original body
errorHandler(deadLetterChannel("jms:queue:dead")
       .useOriginalMessage().maximumRedeliveries(5).redeliveryDelay(5000);

6.3.8. 지연 패턴 재전송

Apache Camel 2.0에서 사용 가능한 delay 옵션은 redelivery count의 특정 범위에 대한 지연을 지정하는 데 사용됩니다. 지연 패턴에는 다음과 같은 구문이 있습니다. limit1:delay1;limit2:delay2;limit3:delay3 ;…​ ...여기서 각 delayN 은 범위limitN cidr redeliveryCount < limitN+1 의 재전송에 적용됩니다.

예를 들어 다음 재전송 지연에 세 개의 그룹을 정의하는 5:1000;10:5000;20:20000 패턴을 고려해 보십시오.

  • 1..4 = 0밀리초(첫 번째 그룹이 5로 시작됨)를 시도합니다.
  • 5..9 = 1000밀리초(첫 번째 그룹)를 시도합니다.
  • 10..19 = 5000밀리초(두 번째 그룹)를 시도합니다.
  • 20.. = 20000밀리초(마지막 그룹)를 시도합니다.

limit 1로 그룹을 시작하여 시작 지연을 정의할 수 있습니다. 예를 들어 1:1000;5:5000 으로 인해 다음과 같은 재전송 지연이 발생합니다.

  • 1..4 = 1000 밀리 밀리 (첫 번째 그룹)
  • 시도 번호 5.. = 5000 밀리 밀리 (마지막 그룹)

다음 지연이 이전보다 커야 한다는 요구 사항은 없으며 원하는 지연 값을 사용할 수 있습니다. 예를 들어 지연 패턴 1:5000;3:1000 은 5초 지연으로 시작하고 지연을 1초로 줄입니다.

6.3.9. 어떤 엔드 포인트가 실패합니까?

Apache Camel이 메시지를 라우팅하면 교환이 전송된 마지막 끝점이 포함된 Exchange 속성을 업데이트합니다. 따라서 다음 코드를 사용하여 현재 교환의 최신 대상에 대한 URI를 얻을 수 있습니다.

// Java
String lastEndpointUri = exchange.getProperty(Exchange.TO_ENDPOINT, String.class);

여기서 Exchange.TO_ENDPOINTCamelToEndpoint 와 동일한 문자열 상수입니다. 이 속성은 Camel이 모든 엔드포인트에 메시지를 전송할 때마다 업데이트됩니다.

라우팅 중에 오류가 발생하고 교환이 dead letter queue로 이동되는 경우 Apache Camel은 오류가 발생하기 전에 교환이 전송된 마지막 대상을 식별하는 CamelFailureEndpoint 라는 속성을 추가로 설정합니다. 따라서 다음 코드를 사용하여 dead letter 큐 내에서 실패 끝점에 액세스할 수 있습니다.

// Java
String failedEndpointUri = exchange.getProperty(Exchange.FAILURE_ENDPOINT, String.class);

여기서 Exchange.FAILURE_ENDPOINTCamelFailureEndpoint 와 동일한 문자열 상수입니다.

참고

이러한 속성은 지정된 대상 끝점 처리가 완료된 후에도 오류가 발생하더라도 현재 교환에 설정된 상태로 유지됩니다. 예를 들어 다음 경로를 고려하십시오.

        from("activemq:queue:foo")
        .to("http://someserver/somepath")
        .beanRef("foo");

이제 foo 8080에서 오류가 발생한다고 가정합니다. 이 경우 Exchange.TO_ENDPOINT 속성 및 Exchange.FAILURE_ENDPOINT 속성에는 값이 계속 포함됩니다.

6.3.10. OnRedelivery 프로세서

dead letter 채널이 redeliveries를 수행할 때 모든 재전송 시도 바로 전에 실행되는 Processor 를 구성할 수 있습니다. 이는 메시지를 재전송하기 전에 변경해야 하는 경우에 사용할 수 있습니다.

예를 들어 다음 dead letter 채널은 교환을 재전송하기 전에 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);
    }
}

6.3.11. 종료 또는 중지 중 재전송 제어

경로를 중지하거나 정상 종료를 시작하는 경우 오류 처리기의 기본 동작은 재전송을 계속 시도하는 것입니다. 일반적으로 원하는 동작이 아니므로 다음 예와 같이 allowRedeliveryWhileStopping 옵션을 false 로 설정하여 종료 또는 중지 중에 재전송을 비활성화할 수 있습니다.

errorHandler(deadLetterChannel("jms:queue:dead")
    .allowRedeliveryWhileStopping(false)
    .maximumRedeliveries(20)
    .redeliveryDelay(1000)
    .retryAttemptedLogLevel(LoggingLevel.INFO));
참고

이전 버전과의 호환성을 위해 allowRedeliveryWhileStopping 옵션은 기본적으로 적용됩니다. 그러나 공격적인 종료 중에 이 옵션 설정에 관계없이 재전송이 항상 억제됩니다(예: 정상 종료 후 시간 초과).

6.3.12. onExceptionOccurred Processor 사용

dead Letter 채널은 예외가 발생한 후 메시지의 사용자 지정 처리를 허용하도록 onExceptionOccurred 프로세서를 지원합니다. 사용자 지정 로깅에도 사용할 수 있습니다. onExceptionOccurred 프로세서에서 발생한 새로운 예외는 WARN으로 기록되고 기존 예외를 재정의하지 않도록 무시합니다.

onRedelivery 프로세서와 onExceptionOccurred 프로세서의 차이점은 재전송 시도 직전에 이전을 처리할 수 있다는 것입니다. 그러나 예외가 발생한 직후에 발생하지 않습니다. 예를 들어 재전송 시도 사이에 5초 지연을 수행하도록 오류 처리기를 구성하는 경우 예외가 발생한 후 5초 후에 재전송 프로세서가 호출됩니다.

다음 예제에서는 예외가 발생할 때 사용자 지정 로깅을 수행하는 방법을 설명합니다. 사용자 지정 프로세서를 사용하려면 onExceptionOccurred를 구성해야 합니다.

errorHandler(defaultErrorHandler().maximumRedeliveries(3).redeliveryDelay(5000).onExceptionOccurred(myProcessor));

6.3.13. onException 절

경로 빌더에서 errorHandler() 인터셉터를 사용하는 대신 다양한 예외 유형에 대해 다양한 재전송 정책 및 다양한 dead letter 채널을 정의하는 일련의 onException() 절을 정의할 수 있습니다. 예를 들어 각 NullPointerException,IOExceptionException 유형에 대해 고유한 동작을 정의하려면 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. “Redelivery Policy Settings”에 나열된 대로)를 연결하여 재전송 옵션이 지정되고 to() DSL 명령을 사용하여 dead letter channel의 끝점을 지정합니다. onException() 절에서 다른 Java DSL 명령을 호출할 수도 있습니다. 예를 들어 이전 예제에서는 setHeader() 를 호출하여 messageInfo 라는 메시지 헤더에 일부 오류 세부 정보를 기록합니다.

이 예에서는 NullPointerExceptionIOException 예외 유형이 특별히 구성됩니다. 기타 모든 예외 유형은 일반 Exception 예외 인터셉터에서 처리합니다. 기본적으로 Apache Camel은 throw된 예외와 가장 근접하게 일치하는 예외 인터셉터를 적용합니다. 정확한 일치 항목을 찾지 못하면 가장 가까운 기본 유형과 일치하도록 시도합니다. 마지막으로 다른 인터셉터와 일치하지 않는 경우 Exception 유형의 인터셉터는 나머지 모든 예외와 일치합니다.

6.3.14. OnPrepareFailure

교환을 dead letter 큐로 전달하기 전에 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"/>