[원문출처] http://camel.apache.org/dead-letter-channel.html


해당 문서의 경우 Apache 재단의 Camel Document 문서 일부를 번역 한 것입니다. 
번역본인 해당 문서의 경우 역자에게 있음을 알리며 상업적 이용을 불허합니다. 

www.sogomsoft.co.kr (주) 소곰소프트 




데드 레터 채널(Dead Letter Channel)

Camel은 Error Handler인 DeadLetterChannel를 사용하여 EIP patterns으로 부터 Dead Letter Channel을 지원한다.

데드 레터 채널 (Dead Letter Channel) 과 기본 에러 핸들러의 차이

기본 에러 핸들러는 매우 작다:  Exchange가 즉시 끝나고 호출한 곳에 던져진 Exception을 전달한다.

데드 레터 채널(Dead Letter Channel)은 재 전송을 포함하는 행위(호출한 곳에 던져진 Exception을 전파 할 것인지 여부, 실패 했을때 전달 해야 할 곳 등)을 조절 하는 것을 허용한다.

데드 레터 채널(Dead Letter Channel)은 또한 로그에서 verbose 모드가 아니게 환경 구성되어 있다. 그래서 메시지가 핸들링되고 데드 레터 엔드포인트에 옮겨 질 때 아무것도 로깅 되지 않는다. 만약 몇몇 로깅 레벨을 원하면 재전송 정책 / 환경 구성하기 위한 데드 레터 채털 상에 다양한 옵션을 사용할 수 있다. 예를 들면 만약 메시지 히스토리를 원하면 logExhaustedMessageHistory=true를 설정한다. (Camel 2.15.x 나 그 이후 버전은 logHandled=true로 설정한다. ).

DeadLetterChannel 이 데드 레터 엔드포인트에 메시지를 이동할 때 역시 기본적으로 데드 레터 채널에 의해 새로운 Exception이 핸들링 된다.  DeadLetterChannel이  항상 성공 하는 것을 보장한다. Camel 2.15 이후부터 이 행위는 deadLetterHandleNewException=false 옵션을 설정함으로써 변경 될 수 있다. 그래서 만약 새로운 Exception이 던져지게 될 때 데드 레터 채털(dead letter channel)은 실패하게 되고 기본 에러 핸들러의 행위인 새 Exception을 다시 돌려준다. (역주: 데드 레터 채널은 항상 성공이고 에러를 돌려주지 않으나 기본 에러 핸들러는 에러 발생시 바로 호출 한 곳으로 에러를 전파한다.). 새 Exception이 발생 했을 때 데드 레터 채널(dead letter channel) WARN 레벨로 로깅한다. logNewException=false를 설정함으로써 이 기능을 끌 수 있다..

재전송

임시 정전 또는 데이터베이스 데드락 때문에 메시지를 처리 하는데 실패 하는 원인이 되는 것이 일반적이다.그러나 만약 몇 초의 딜레이로 몇번  재시도 하면 완전히 좋게 될 것이다. 그래서 일반적으로 재전송을 시도하기 전에 얼마나 많이 메시지를 재 전송하기를 원하는지 그리고 얼마나 오래 기다릴 것인지 같은 몇몇 재 전송 정책을 사용하는 것을 원한다.

재 전송 정책(RedeliveryPolicy)은 재전송될 메시지를 어떻게 할 것인지 정의한다. 다음처럼 커스터마이징 할 수 있다.

  • 실패로 간주하고 데드 레터 채널로 보내기 전에 얼마나 많이 재 전송되는 것을 시도 하는가
  • 초기화 재 전송 타임아웃 
  • 지수 백오프 알고리즘을 사용하는지 아닌지 ( 예를 들면, 백오프 멀티플라이어를 사용하여 재시도 시간 간격 증가)
  • 충돌을 피하기 위해 타이밍에 약간의 랜덤을 추가할 것인지
  • 지연 패턴 (자세한 내용은 아래 참고)
  • Camel 2.11: 멈추는 중이거나 셧다운 중일 때 재 전송을 허용할 것인지 아닌지

메시지를를 재 전송하는 모든 시도가 실패하면 메시지는 데드 레터 큐(dead letter queue)로 보내진다.

Exchange가 데드 레터 큐(dead letter queue)에 옮겨지고 처리되는 방법

Dead Letter Channel 에서 처리

모든 재 전송 시도가 실패 했을때 Exchange 가 데드 레터 큐(dead letter queue: dead letter endpoint)로 이동된다. Exchange는 완료되고 뷰의 클라이언트 포인트로부터 처리되게 될 것이다.이와 같이Dead Letter Channel이 Exchange를 처리한다.

예를 들면 다음처럼 데드 레터 채널에 환경 구성한다. :

Fluent Builders를 사용


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

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>

위 Dead Letter ChannelExchange.EXCEPTION_CAUGHT 을 키값으로 하는 Exchange의 프로퍼티에 발생된 Exception을 옮김으로써 발생된 Exception(setException(null))을 클리어한다. 그러면 Exchange는 "jms:queue:dead" 에 옮겨지게 되고 클라이언트는 실패를 통보하지 않는다.

Exchange가 데드 레터 큐(dead letter queue)에 옮겨지고  원본 메시지를 사용하는 방법

useOriginalMessage 옵션이 라우팅 하는 동안 잠재적으로 수정된 현재의 메시지 대신에 원본 입력 메시지를 라우팅 하기 위해 사용된다.

예를 들면 다음 라우터를 사용하면 :


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

이 라우터는 JMS 메시지를 리스닝하고 검증하고 변형하고 처리 한다. 이 Exchange 페이로드가 변형되고 변경되는 동안 이 경우 무언가 잘못되었기 때문에 다른 JMS destination으로 메시지를 옮기기를 원한다. 그러면 우리는 useOriginalMessage 옵션으로 Dead Letter Channel 에 환경 구성할 수 있다.  그러나 destination에 Exchange 가 옮겨졌을 때 이 안에 있는 메시지의 상태를 우리는 알지 못한다. transformOrder 이전에 또는 이후에 에러가 발생했는가? 그래서 jms:queue:order:input 로부터 받은 원본 입력 메시지를 옮기는 것을 원하는지 확인 해야 한다. 그래서 아래처럼 useOriginalMessage 옵션을 활성화함으로써 가능하다.:


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

그러면 jms:queue:dead에 라우팅된 메시지는 원본 입력 값이다. 만약 수동으로 재 시도 하는 것을 원하면 Input 큐에 JMS 메시지를 옮길 수 있다. 메시지가 아무런 문제가 없다면 메시지는 우리가 수신 받은 원본과 같다.

OnRedelivery

Dead Letter Channel이 재 전송 중 일 때, 매번 재 전송을 시도하기 전에 실행되는 Processor를 환경 구성 하는 것이 가능하다. 재전송 되기 전에 메시지를 바꿀 필요가 있는 상황에서 사용 될 수 있다. 아래 다음 예제를 보라..

onException 과 onRedeliver

onException 당 onRedeliver를 설정하는 것을 지원한다. 이 말의 의미는 다른 Exception을 위해 특정 재 전송을 할 수 있다는 것이다. 반대로 Dead Letter Channel에 onRedelivery 를 설정하는 것은 위해 글로벌 스코프처럼 보여 질 수 있다.

재전송 기본 값 (Redelivery default values)

기본적으로 재 전송은 비활성화 되어 있다.

기본 재 전송 정책은 다음 값을 사용 하게 될 것이다. :

  • maximumRedeliveries=0
  • redeliverDelay=1000L (1 초)
  • maximumRedeliveryDelay = 60 * 1000L (60 초)
  • 그리고 지수 백오프 알고리즘과 충돌 방지가 꺼져있다.
  • retriesExhaustedLogLevel이 LoggingLevel.ERROR로 설정되어 있다.
  • retryAttemptedLogLevel이 LoggingLevel.DEBUG로 설정되어 있다.
  • Stack traces 가 Camel 2.2 이후부터 소모된 메시지를 위해 로깅된다.
  • Camel 2.3 이후 부터 처리된 exception들은 로딩되지 않는다. 
  • 기본 에러 핸들러를 위해 logExhaustedMessageHistory가 true이다. 그리고 데드 레터 채널(dead letter channel)을 위해 false 이다.
  • Camel 2.17: logExhaustedMessageBody은 기본적으로 민감한 메시지의 Body와 Header 상세 로깅을 피하기 위해  비활성화 되어 있다.만약 이 옵션이 true이면, logExhaustedMessageHistory 옵션도 true여야만 한다..

최대 재 전송 지연 시간은 그 값 보다 지연시간이 더 길지 않다는 것을 보장한다.  기본값 1분. 지수 오프백 알고리즘이 켜져 있다면 발생 할 수 있다.

최대 재 전송은 재 전송을 시도할  수 이다. 기본적으로 Camel은 Exchange 1 + 5회를 처리 하기 위해 시도한다. 일반 시도 1회, 재전송으로 5회 시도한다. 
maximumRedeliveries에 -1과 같은 음수를 설정하면 항상 재 전송한다. (제한 없음).
maximumRedeliveries에 0을 설정하면 어떠한 재 전송 시도도 비활성화 된다.

Camel은 기본적으로 DEBUG 레벨에 전송 실패를 로깅하게 될 것이다. retriesExhaustedLogLevel 또는 를 지정함으로써 변경 할 수 있다. 예지는 ExceptionBuilderWithRetryLoggingLevelSetTest 를 보라 

stack traces의 로깅을 켜고 끌수 있고 만약 꺼져있다면 Camel은 여전히 재 전송 시도를 로깅 하게 될 것이다. 단지 많이 덜 자세한 로그이다.

재 전송 지연 패턴 

지연 패턴은 지연을 위한 범위 패턴을 설정하기 위한 단일 옵션처럼 사용된다. 만약 사용되면 다음 옵션(delay, backOffMultiplier, useExponentialBackOff, useCollisionAvoidance, maximumRedeliveryDelay)은 적용되지 않는다.

다음 구문을 사용하여 범위의 그룹을 설정한다. : limit:delay;limit 2:delay 2;limit 3:delay 3;...;limit N:delay N

각각의 그룹은 콜론으로 구분된 두 값을 가진다.

  • limit = 상위 제한 limit
  • delay = 밀리세컨드 지연시간
    그리고 그룹은 다시 세비 콜론으로 구분된다.
    다음 그룹이 이전 그룹보다 더 높은 제한을 가져야 하는 것은 경험 법칙이다.

다음 예로 명확하게 해보자:
delayPattern=5:1000;10:5000;20:20000

3개 그룹들이 주어졌다.:

  • 5:1000
  • 10:5000
  • 20:20000

재 전송 시도를 위한 3개의 지연시간의 결과:

  • 재 전송 시도 1..4회차 = 0 millis (첫번째 그룹이 5부터 시작 하므로)
  • 재 전송 시도 5..9회차 = 1000 millis (첫번째 그룹)
  • 재 전송 시도 10..19회차 = 5000 millis (두번째 그룹)
  • 재 전송 시도 20..회차 이상 = 20000 millis (마지막 그룹)

주의 : 첫번째 재 전송 시도는 1 이다 첫번째 그룹은 1부터 시작하거나 더 커야 한다. 

제한이 1부터 시작하는 그룹으로 지연 시간이 시작 될 수 있다. : delayPattern=1:1000;5:5000

  • 재 전송 시도 1..4회차 = 1000 millis (첫번째 그룹)
  • 재 전송 시도 5..회차 이상 = 5000 millis (마지막 그룹)

다음 지연시간이 이전 보다 높아야 할 필요는 없다. 원하는 지연 시간 값을 사용할 수있다. 예를 들면  delayPattern=1:5000;3:1000, 5초 단위로 지연 시간을 가지고 시작해서 마지막에는 1초 단위로 감소한다. 

재 전송 헤더 (Redelivery header)

메시지가 재 전송 될 때, DeadLetterChannel은 얼마나 많은 재 전송 시도를 하였는지 표시하기 위해 메시지에 사용자 정의 헤더를 추가 한다. 
Camel 2.6 이전 버전: 헤더는 Exchange.REDELIVERY_COUNTER에 정의된 CamelRedeliveryCounter이다.

Camel 2.6 이후 부터: 헤더는 최대 전송을 설정 하는 것을 포함하는 Exchange.REDELIVERY_MAX_COUNTER에 정의된 CamelRedeliveryMaxCounter이다. 만약 retryWhile 을 사용하거나 또는 환경 구성된 최대 재 전송에 제한이 없으면 이 헤더 값은 존재 하지 않는다. 

그리고 불린 값으로 재 전송 될지 아닌지를 판단. (첫번째 시도에서)
헤더 값 CamelRedelivered은 불린 값으 포함한다. 만약 재 전송 되거나 아니거나 이면 Exchange.REDELIVERED 에 정의 되어 있다. 

exchange로 부터 동적으로 계산된 지연 시간.
Camel 2.9 또는 2.8.2에서 : 헤더 값 CamelRedeliveryDelayExchange.REDELIVERY_DELAY에 정의 되어 있고 이 헤더 값이 없으면 일반 재 전송 률이 적용된다..

어느 엔드포인트가 실패인가?

Camel 2.1부터 가능 한 것

Camel이 메시지를 라우터 할 때 마지막 엔드포인트를 포함하는 속성을 가진 Exchange 장식한다. Camel은 Exchange 를 보낸다. :


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

Exchange.TO_ENDPOINT 는 CamelToEndpoint 상수 값을 가진다. .

이 정보는 Camel이 어떤 엔드 포인트에 메시지를 보낼때 업데이트 된다. 그래서 만약 존재 하면 카멜이 Exchange를 보낸 마지막 엔드 포인트 이다. 

예를 들면 주어진 Endpoint 에 Exchange를 처리하거나 그 메시지가 데드 레터 큐(dead letter queue)로 옮겨 질 때  Camel은 또한 마지막 엔드포인트를 포함하는 또 다른 프로퍼티를 Exchange를 장식한다.:


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

Exchange.FAILURE_ENDPOINT 는 CamelFailureEndpoint 상수 값을 가진다..

예를 들면 데드 레터 큐(dead letter queue)에 정보를 패치하고 에러 리포팅을 위해 사용하는 것을 허용한다. 

만약 Camel 라우터가 동적 Recipient List  같은 동적 비트이면 실패한 엔드포인트를 알 수 있다.

주의: 그 정보들은 주어진 엔드포인트에 의해 심지어 메시지가 성공적으로 처리되더라도  Exchange에 유지된다. 그리고 예를 들면 로컬 Bean 에서 마지막 실패는 대신 처리 된다.  그래서 정확한 에러를 돕는 힌트를 조심해야 한다. 

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

이제 위 라우터가 foo 빈에서 실패가 발생하는 것을 가정한다. 그러면 Exchange.TO_ENDPOINT 와 Exchange.FAILURE_ENDPOINT가 여전히 http://someserver/somepath 값을 포함하게 될 것이다 .

OnPrepareFailure

Camel 2.16부터 가능

exchange가 데드 레터 큐(dead letter queue)에 보내지기 전에, you can use onPrepare to allow a custom Processor to prepare the exchange를 준비하기 위해 Exchange가 왜 실패 했는지 정보를 추가 하는 것 같은 사용자 정의 Processor 를 허용하기 위해  onPrepare 를 사용 할 수 있다.  예를 들면 다음 프로세서는 Exception 메시지를 헤더에 추가한다.

public static 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()));

 

XML DSL으로 환경 구성하는 것은 다음 보여지는 것과 같다. :

<bean id="myPrepare"
      class="org.apache.camel.processor.DeadLetterChannelOnPrepareTest.MyPrepareProcessor"/>
 
 
  <errorHandler id="dlc" type="DeadLetterChannel" deadLetterUri="jms:dead" onPrepareFailureRef="myPrepare"/>

 

The onPrepare is also available using the default error handler.

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.

stopping/shutdown 되는 동안 재 전송이 허용될 때 컨트롤

Camel 2.11이후부터 가능

Camel 2.10 이전은, Camel 라우터가 멈추거나 Camel이 셧다운 되는 동안 재 전송을 수행하게 될 것이다. 이것이 Camel 2.10는 적극적으로 셧다운되는동안은 재 전송 시도를 수행하지 않는 것 처럼 조금 개선 되었다. (예를 들면 Graceful Shutdown 동안 그리고 타임아웃을 입력하면). Camel 2.11 이후부터 재 전송을 허용하는지 아닌지 컨트롤 하기 위해 사용할 수 있는 새 옵션 allowRedeliveryWhileStopping이 있다.; 어진행중엔 어떠한 재 전송도 여전히 실행 되는 것에 주의하라 이 옵션은 단지 route가 멈추는 중이거나 / Camel이 shutdown이 실행된 이후에  어떤 재 전송이 실행되는 것은 허용하지 않는다. 만약 재 전송이 허용되지 않는다면, Exchange 에 RejectedExcutionException 설정 될 것이고  Exchange 정지 처리를 한다. 그 의미는 모든 Comsumer는 RejectedExecutionException 실패로 Exchange 를 보게 될 것이다.   

역주) Route가 정지되거나 Camel이 셧다운되면 진행중인 프로세스는 모드 Exchange RejectedExcutionException 으로 오류 처리 한 후에 정지 및 셧다운이 수행된다.

이전 처럼 이전 버전과 호환 가능하게 되는 것은 기본 값은 true이다 to be backwards compatible as before. 예를 들면 다음 예제는 어떻게  Java DSL 과 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"));

그리고 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>

샘플

다음 예제는 DSL을 사용하여 Dead Letter Channel을  어떻게 환경 구성하는지를 보여 준다. 

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");
    }
};

이 예제로 RedeliveryPolicy을 환경 구성할 수 있다.

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");
    }
};

어떻게 재 전송 전에 Exchange 를 변경 할 수 있는가?

Dead Letter Channel 에서 직접적으로 각각의 재 전송 시도 이전에 수행되어야 할 Processor 를 설정 하는 것을 지원한다. 

Dead Letter Channel이 재 전송이 수행 될 때, 모든 재 전송 시도 이전에 단지 실행할 Processor 를 환경 구성하는 것이 가능하다. 재 전송 이전에 메시지를 변경할 필요가 있을 때 어느 상황에서나 사용 될 수 있다. 

여기 각각의 재 전송 이전에 수행될 MyRedeliveryProcessor 를 사용하기 위해 Dead Letter Channel을 환경 구성한다.

// 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));

그리고 메시지를 변경할 곳에서 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);
    }
}

무슨 이유로 데드 레터 채널이 호출 되었는지 어떻게  로그 기록 할 수 있나 ?

종종 무엇이 잘못되어 데드 레터 채널이 발생되어 갔었는지 알 필요가 있다. 그러나 이 목적을 로깅 하는 것을 제공하지는 않는다. 그래서 Dead Letter Channel의 엔드포인트는 자신의 엔드 포인트에 설정 될 필요가 있다. (direct:deadLetterChannel 처럼). 우리는 실패한 Exchange 를 옮기기를 원하는 곳 앞에 Exchange 를 받아 들이고 Exception을 로그 기록하기 위해 라우터를 작성한다. (예를 들면 DLQ 큐라 할지라도).  http://stackoverflow.com/questions/13711462/logging-camel-exceptions-and-sending-to-the-dead-letter-channel를 보라

이패턴 사용하기 

만약 EIP 패턴을 사용하는 것을 원하면 Getting Started를 읽어라. 아마 특별히 Endpoint 와 URIs 상세한 묘사하는 유용한 아키텍처를 찾을 수 있을 것이다.  그러면 당신은 패턴을 찾는 것을 시도하기 전에 이 예제(Examples )를 시도할 수 있습니다. 




+ Recent posts