spring - retry 재 시도 및 퓨즈 상세 설명 - 내용 보충

27766 단어 짜임새spring-retry
본 고 는 제6 장 시간 초과 와 재시험 메커니즘 보충 내용 이다.spring - retry 프로젝트 는 재 시도 와 퓨즈 기능 을 실현 하여 현재 SpringBatch, Spring Integration 등 프로젝트 에 사용 되 고 있 습 니 다.Retry Operations 는 재 시도 API 를 정의 합 니 다. Retry Template 는 템 플 릿 을 제공 합 니 다. 스 레 드 가 안전 하고 Spring 의 일 관 된 API 스타일 과 같 습 니 다. Retry Template 는 재 시도, 퓨즈 기능 을 템 플 릿 에 밀봉 하여 건장 하고 실수 하기 쉬 운 API 를 제공 합 니 다.우선, Retry Operations 인터페이스 API:
public interface RetryOperations {
   <T, E extends Throwable>T execute(RetryCallback<T, E>retryCallback) throws E;
   <T, E extends Throwable>T execute(RetryCallback<T, E>retryCallback, RecoveryCallback<T> recoveryCallback) throws E;
   <T, E extends Throwable>T execute(RetryCallback<T, E>retryCallback, RetryState retryState) throws E, ExhaustedRetryException;
   <T, E extends Throwable>T execute(RetryCallback<T, E>retryCallback, RecoveryCallback<T> recoveryCallback, RetryStateretryState) throws E;
}

Retry Callback 을 통 해 재 시도 해 야 할 업무 서 비 스 를 정의 합 니 다. 재 시도 가 최대 재 시도 시간 이나 최대 재 시도 횟수 를 초과 하면 Recovery Callback 을 호출 하여 복원 할 수 있 습 니 다. 예 를 들 어 가짜 데이터 나 트 레이 데 이 터 를 되 돌려 주 는 것 입 니 다.그럼 언제 다시 해 볼 까요?spring - retry 는 관련 이상 을 던 진 후 재 시도 정책 을 실행 합 니 다. 재 시도 정책 을 정의 할 때 재 시도 해 야 할 이상 을 정의 해 야 합 니 다.읽 기 동작 만 다시 시도 할 수 있 고, 멱 등 쓰기 동작 은 다시 시도 할 수 있 으 나, 비 멱 등 쓰기 동작 은 다시 시도 할 수 없 으 며, 다시 시도 하면 더러 운 쓰기 나 중복 데이터 가 발생 할 수 있 습 니 다.재 시도 전략 은 어떤 것들 이 있 나 요?spring - retry 는 다음 과 같은 재 시도 정책 을 제공 합 니 다.Retry Policy 는 다음 과 같은 전략 을 제공 합 니 다.
NeverRetry Policy: Retry Callback 을 한 번 만 호출 할 수 있 습 니 다. 다시 시도 할 수 없습니다.
AlwaysRetry Policy: 성공 할 때 까지 무한 재 시도 할 수 있 습 니 다. 이 방식 의 논리 가 부당 하면 순환 을 초래 할 수 있 습 니 다.
Simple Retry Policy: 고정 횟수 재 시도 정책, 기본 재 시도 최대 횟수 는 3 회, Retry Template 기본 사용 정책;
TimeoutRetry Policy: 시간 초과 재 시도 정책 입 니 다. 기본 시간 초과 시간 은 1 초 입 니 다. 지정 한 시간 초과 시간 내 에 재 시도 할 수 있 습 니 다.
CircuitBreakerRetry Policy: 녹 아내 리 는 기능 이 있 는 재 시도 정책 입 니 다. 3 개의 인자 openTimeout, resetTimeout, delegate 를 설정 하고 나중에 이 정책 을 자세히 소개 해 야 합 니 다.
Composite Retry Policy: 조합 재 시도 전략 은 두 가지 조합 방식 이 있 습 니 다. 낙관적 인 조합 재 시도 전략 은 하나의 전략 만 있 으 면 재 시도 가 가능 하 다 는 것 을 말 합 니 다. 비관 적 인 조합 재 시도 전략 은 하나의 전략 만 있 으 면 재 시도 가 허용 되 지 않 으 면 되 지만 어떤 조합 방식 이 든 조합 중의 모든 전략 이 실 행 됩 니 다.
다시 시도 할 때의 회피 전략 은 무엇 입 니까?즉시 재 시도 할 지, 아니면 한 동안 기 다 렸 다가 다시 시도 할 지, 예 를 들 어 네트워크 오류, 즉시 재 시도 하면 즉시 실패 할 수 있 으 므 로 잠시 기 다 렸 다가 다시 시도 하 는 것 이 좋 습 니 다. 또한 많은 서비스 가 동시에 재 시도 되 어 DDoS 가 발생 하 는 것 을 방지 해 야 합 니 다.BackOffPolicy 는 다음 과 같은 정책 을 제공 합 니 다.
NoBack OffPolicy: 회피 알고리즘 정책 이 없습니다. 즉, 재 시도 할 때 바로 재 시도 합 니 다.
Fixed Back OffPolicy: 고정 시간의 회피 정책 은 인자 sleeper 와 backOffPeriod 를 설정 해 야 합 니 다. sleeper 는 대기 정책 을 지정 합 니 다. 기본 값 은 Thread. sleep 입 니 다. 즉, 스 레 드 휴면, backOffPeriod 는 휴면 시간 을 지정 합 니 다. 기본 값 은 1 초 입 니 다.
UniformRandomBackOffPolicy: 무 작위 시간 회피 정책 은 sleeper, minBackOffPeriod 와 maxBackOffPeriod 를 설정 해 야 합 니 다. 이 정책 은 [minBackOffPeriod, maxBackOffPeriod 사이 에 무 작위 휴면 시간 을 가 져 옵 니 다. minBackOffPeriod 는 기본 500 밀리초, maxBackOffPeriod 는 기본 1500 밀리초 입 니 다. ExponentialBack OffPolicy: 지수 회피 정책 은 인자 sleeper, initialInterval, maxInterval 과 multiplier 를 설정 해 야 합 니 다. initialInterval 은 초기 휴면 시간 을 지정 하고 기본 100 밀리초, maxInterval 은 최대 휴면 시간 을 지정 합 니 다. 기본 30 초, multiplier 는 곱 수 를 지정 합 니 다. 즉, 다음 휴면 시간 은 현재 휴면 시간 * multiplier 입 니 다. ExponentialRandomBackOffPolicy: 랜 덤 지수 회피 전략 으로 랜 덤 곱 수 를 도입 합 니 다. 전에 말 했 듯 이 고정 곱 수 는 많은 서 비 스 를 동시에 재 시도 하여 DDoS 를 초래 할 수 있 습 니 다. 랜 덤 휴면 시간 을 사용 하여 이런 상황 을 피 할 수 있 습 니 다. 이제 기본 적 인 개념 은 끝 났 습 니 다. 다음은 Retry Template 의 주요 절 차 를 살 펴 보 겠 습 니 다.
protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,
      RecoveryCallback<T> recoveryCallback, RetryState state)
      throws E, ExhaustedRetryException {
   //    
   RetryPolicy retryPolicy = this.retryPolicy;
   //    
   BackOffPolicy backOffPolicy = this.backOffPolicy;
   //     ,               
   RetryContext context = open(retryPolicy, state);
   try {
      //     ,  RetryListener#open
      boolean running = doOpenInterceptors(retryCallback, context);
      //          
      while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
         try {//  RetryCallback  
            return retryCallback.doWithRetry(context);
         } catch (Throwable e) {//   ,          
            //     ,          
            registerThrowable(retryPolicy, state, context, e);
            //  RetryListener#onError
            doOnErrorInterceptors(retryCallback, context, e);
            //      ,      ,             
            if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
               backOffPolicy.backOff(backOffContext);
            }
            //state != null && state.rollbackFor(context.getLastThrowable())
            //       ,              ,       
            if (shouldRethrow(retryPolicy, context, state)) {
               throw RetryTemplate.<E>wrapIfNecessary(e);
            }
         }
         //        ,  GLOBAL_STATE  ,         ;                    ,       ,CircuitBreakerRetryPolicy       ;
         if (state != null && context.hasAttribute(GLOBAL_STATE)) {
            break;
         }
      }
      //     ,   RecoveryCallback,      ,      
      return handleRetryExhausted(recoveryCallback, context, state);
   } catch (Throwable e) {
      throw RetryTemplate.<E>wrapIfNecessary(e);
   } finally {
      //    
      close(retryPolicy, context, state, lastException == null || exhausted);
      //  RetryListener#close,        
      doCloseInterceptors(retryCallback, context, lastException);
   }
}

상태 있 음 or 상태 없 음
상태 없 이 다시 시도 하 는 것 은 하나의 순환 에서 재 시도 정책 을 실행 하 는 것 입 니 다. 즉, 컨 텍스트 를 한 스 레 드 컨 텍스트 에 유지 하고 한 번 호출 에서 완전한 재 시도 전략 판단 을 하 는 것 입 니 다. 원 격 으로 검색 방법 을 호출 할 때 가장 흔히 볼 수 있 는 무 상태 재 시도 입 니 다.
RetryTemplate template = new RetryTemplate();
//    :      
RetryPolicy retryPolicy = new SimpleRetryPolicy(3);
template.setRetryPolicy(retryPolicy);
//    :      
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(100);
backOffPolicy.setMaxInterval(3000);
backOffPolicy.setMultiplier(2);
backOffPolicy.setSleeper(new ThreadWaitSleeper());
template.setBackOffPolicy(backOffPolicy);

//      ,    
String result = template.execute(new RetryCallback<String, RuntimeException>() {
    @Override
    public String doWithRetry(RetryContext context) throws RuntimeException {
        throw new RuntimeException("timeout");
    }
});
//      ,  RecoveryCallback
String result = template.execute(new RetryCallback<String, RuntimeException>() {
    @Override
    public String doWithRetry(RetryContext context) throws RuntimeException {
        System.out.println("retry count:" + context.getRetryCount());
        throw new RuntimeException("timeout");
    }
}, new RecoveryCallback<String>() {
    @Override
    public String recover(RetryContext context) throws Exception {
        return "default";
    }
});

상태 재 시도 가 있 습 니 다. 두 가지 경우 상태 재 시도 가 필요 합 니 다. 트 랜 잭 션 작업 은 스크롤 백 이나 퓨즈 모드 가 필요 합 니 다.  트 랜 잭 션 작업 에 스크롤 백 장면 이 필요 할 때 전체 작업 에서 데이터베이스 이상 DataAccessException 을 던 지면 다시 시도 할 수 없고 다른 이상 을 던 지면 다시 시도 할 수 있 으 며 Retry State 를 통 해 이 루어 질 수 있 습 니 다.
//       ,         ,   key    
Object key = "mykey";
//                    ,     (             )
boolean isForceRefresh = true;
// DataAccessException    
BinaryExceptionClassifier rollbackClassifier =
        new BinaryExceptionClassifier(Collections.<Class<? extends Throwable>>singleton(DataAccessException.class));
RetryState state = new DefaultRetryState(key, isForceRefresh, rollbackClassifier);

String result = template.execute(new RetryCallback<String, RuntimeException>() {
    @Override
    public String doWithRetry(RetryContext context) throws RuntimeException {
        System.out.println("retry count:" + context.getRetryCount());
        throw new TypeMismatchDataAccessException("");
    }
}, new RecoveryCallback<String>() {
    @Override
    public String recover(RetryContext context) throws Exception {
        return "default";
    }
}, state);

Retry Template 에서 상태 재 시도 시 스크롤 백 장면 에서 이상 처리 코드 를 직접 던 집 니 다.
//state != null && state.rollbackFor(context.getLastThrowable())
//       ,              ,       
if (shouldRethrow(retryPolicy,context, state)) {
    throw RetryTemplate.<E>wrapIfNecessary(e);
}

퓨즈 필드 입 니 다. 상태 재 시도 가 있 을 때 전역 모드 입 니 다. 현재 순환 에서 재 시도 하지 않 고 전역 재 시도 모드 (스 레 드 컨 텍스트 가 아 닙 니 다) 입 니 다. 퓨즈 정책 시 테스트 코드 는 다음 과 같 습 니 다.
RetryTemplate template = new RetryTemplate();
CircuitBreakerRetryPolicy retryPolicy =
        new CircuitBreakerRetryPolicy(new SimpleRetryPolicy(3));
retryPolicy.setOpenTimeout(5000);
retryPolicy.setResetTimeout(20000);
template.setRetryPolicy(retryPolicy);

for (int i = 0; i < 10; i++) {
    try {
        Object key = "circuit";
        boolean isForceRefresh = false;
        RetryState state = new DefaultRetryState(key, isForceRefresh);
        String result = template.execute(new RetryCallback<String, RuntimeException>() {
            @Override
            public String doWithRetry(RetryContext context) throws RuntimeException {
                System.out.println("retry count:" + context.getRetryCount());
                throw new RuntimeException("timeout");
            }
        }, new RecoveryCallback<String>() {
            @Override
            public String recover(RetryContext context) throws Exception {
                return "default";
            }
        }, state);
        System.out.println(result);
    } catch (Exception e) {
        System.out.println(e);
    }
}

왜 전역 모드 라 고 하 죠? isForce Refresh 를 false 로 설 정 했 습 니 다. 컨 텍스트 를 가 져 올 때 key "circuit" 에 따라 캐 시 에서 가 져 와 같은 컨 텍스트 를 가 져 옵 니 다.
Object key = "circuit";
boolean isForceRefresh = false;
RetryState state = new DefaultRetryState(key,isForceRefresh);

  RetryTemplate           ,          。
if (state != null && context.hasAttribute(GLOBAL_STATE)) {
   break;
}

퓨즈 정책 설정 코드, CircuitBreakerRetry Policy 는 세 개의 인 자 를 설정 해 야 합 니 다.
delegate: 재 시도 여 부 를 진정 으로 판단 하 는 전략 입 니 다. 재 시도 에 실 패 했 을 때 용 단 정책 을 실행 합 니 다. openTimeout: openWindow, 퓨즈 회로 가 열 리 는 시간 초과 시간 을 설정 합 니 다. openTimeout 을 초과 하면 퓨즈 회로 가 반 열 린 상태 로 변 합 니 다 (주로 한 번 재 시도 에 성공 하면 닫 힌 회로). resetTimeout: timeout, 퓨즈 를 리 셋 하여 다시 닫 는 시간 초과 시간 을 설정 합 니 다. 퓨즈 회로 가 열 렸 는 지 여 부 를 판단 하 는 코드:
public boolean isOpen() {
   long time = System.currentTimeMillis() - this.start;
   boolean retryable = this.policy.canRetry(this.context);
   if (!retryable) {//    
      //         ,        ,     
      if (time > this.timeout) {
         this.context = createDelegateContext(policy, getParent());
         this.start = System.currentTimeMillis();
         retryable = this.policy.canRetry(this.context);
      } else if (time < this.openWindow) {
         //          ,       ,    
         if ((Boolean) getAttribute(CIRCUIT_OPEN) == false) {
            setAttribute(CIRCUIT_OPEN, true);
         }
         this.start = System.currentTimeMillis();
         return true;
      }
   } else {//    
      //            ,       ,     
      if (time > this.openWindow) {
         this.start = System.currentTimeMillis();
         this.context = createDelegateContext(policy, getParent());
      }
   }
   setAttribute(CIRCUIT_OPEN, !retryable);
   return !retryable;
}

위 코드 에서 보 듯 이 spring - retry 의 퓨즈 정책 은 상대 적 으로 간단 합 니 다.
재 시도 에 실 패 했 을 때 퓨즈 가 열 린 시간 창 [0, openWindow) 에서 즉시 녹 습 니 다. 재 시도 에 실 패 했 고 시간 초과 시간 을 지정 한 후 (> timeout) 퓨즈 회로 가 다시 닫 혔 습 니 다. 퓨즈 가 반 쯤 열 린 상태 [openWindow, timeout] 에서 재 시도 에 성공 하면 컨 텍스트 를 리 셋 하고 차단기 가 닫 힙 니 다. CircuitBreakerRetry Policy 의 delegate 는 횟수 에 기반 한 Simple Retry Policy 나 시간 초과 에 기반 한 TimeoutRetry Policy 정책 을 설정 해 야 하 며, 전략 은 부분 모드 가 아 닌 전역 모드 이 므 로 횟수 나 시간 초과 설정 의 합 리 성에 주의해 야 합 니 다.  예 를 들 어 Simple Retry Policy 는 3 회, openWindow = 5s, timeout = 20 s 로 설정 되 어 있 습 니 다. Circuit BreakerRetry Policy 의 극단 적 인 상황 을 살 펴 보 겠 습 니 다. 특수 시간 서열:
1s: retryable = false, 재 시도 에 실 패 했 습 니 다. 차단기 회 로 는 열 린 상태 입 니 다. 끊 기 고 start 시간 을 현재 시간 으로 초기 화 합 니 다. 2s: retryable = false, 재 시도 에 실 패 했 습 니 다. 차단기 회 로 는 열 린 상태 입 니 다. 끊 기 고 start 시간 을 현재 시간 으로 초기 화 합 니 다. 7s: retryable = true 는 다시 시도 할 수 있 음 을 표시 하지만 time = 5s, time > this. openWindow 는 false, CIRCUIT OPEN = false 로 판단 하여 녹 지 않 습 니 다. 이때 재 시도 횟수 = 3 은 최대 재 시도 횟수 와 같 습 니 다. 10s: retryable = false, 재 시도 횟수 > 3, time = 8s, time < this. openWindow 는 false 로 판단 하여 녹 아내 리 고 timeout 시간 이 초과 되 기 전에 녹 아내 린 상태 입 니 다. 이 시간 대 를 설정 해 야 합 니 다. 그렇지 않 으 면 녹 는 시간 이 너무 깁 니 다 (기본 timeout = 20 s). (7s, 20s] 사이 의 모든 재 시도: 10s 의 상황 과 같다. 위 와 같이 재 시도 횟수 가 최대 재 시도 횟수 와 같 고 time = openWindow 의 특수 한 상황 이지 만 실제 장면 에 서 는 이런 상황 이 발생 할 수 없다.  spring - retry 의 재 시도 체 제 는 Hystrix 처럼 실패 율 밸브 값 에 따라 회로 가 열 리 거나 닫 힌 다 는 판단 이 없습니다.  부분 순환 재 시도 메커니즘 이 필요 하 다 면, 여러 개의 Retry Template 를 조합 하여 실현 해 야 한다.  spring - retry 도 주 해 를 제공 합 니 다. @ Enableretry, @ Retryable, @ Recover, @ Backoff, @ CircuitBreaker. 구체 적 으로 는 공식 문 서 를 참고 할 수 있 습 니 다.
통계 분석
spring - retry 는 Retry Listener 를 통 해 차단기 모드 를 실현 하고 기본적으로 Statistics Listener 를 제공 하여 재 시도 작업 통계 분석 데 이 터 를 제공 합 니 다.
RetryTemplatetemplate = new RetryTemplate();
DefaultStatisticsRepository repository = new DefaultStatisticsRepository();
StatisticsListener listener = new StatisticsListener(repository);
template.setListeners(new RetryListener[]{listener});

for (int i = 0; i < 10; i++){
    String result = template.execute(new RetryCallback<String, RuntimeException>() {
        @Override
       public String doWithRetry(RetryContext context) throws RuntimeException {
           context.setAttribute(RetryContext.NAME,"method.key");
            return "ok";
        }
    });
}
RetryStatistics statistics = repository.findOne("method.key");
System.out.println(statistics);

"method. key" 와 같은 name 을 정의 하여 이 작업 의 통계 분석 데 이 터 를 조회 해 야 합 니 다.  여기 서 spring - retry 재 시도 와 퓨즈 를 소개 합 니 다. spring - retry 프로젝트 주소https://github.com/spring-projects/spring-retry。  또한 의 과 를 참고 하여 더 많은 내용 을 이해 하고 학습 할 수 있다.

좋은 웹페이지 즐겨찾기