Retry Pattern 개선

날아올라돼지야·2024년 8월 29일
0

1. Retry Pattern을 개선하기 위한 요구 사항

이번 강의에서는 이미 구현된 Retry Pattern을 더욱 개선하는 방법을 다룹니다. 현재 getBuildInfo() 메서드에서 발생하는 모든 예외에 대해 재시도 작업이 수행됩니다. 하지만 특정 예외(예: NullPointerException)에 대해서는 재시도를 하지 않도록 요구될 수 있습니다. 이러한 시나리오를 처리하는 방법을 배우겠습니다.

2. 특정 예외에 대해 Retry를 무시하기

특정 예외가 발생할 때 재시도를 하지 않도록 설정하려면 application.yml 파일에서 ignoreExceptions 속성을 사용합니다.

2.1 ignoreExceptions 설정

application.yml 파일에서 ignoreExceptions를 추가하여 NullPointerException이 발생했을 때 재시도하지 않도록 설정합니다.

resilience4j:
  retry:
    configs:
      default:
        max-attempts: 3
        wait-duration: 100ms
        exponential-backoff:
          enabled: true
          multiplier: 2
        ignoreExceptions: 
          - java.lang.NullPointerException
  • ignoreExceptions: 여기서 NullPointerException을 무시하도록 설정했습니다. 즉, 이 예외가 발생할 경우 재시도하지 않습니다.

3. 재시도 할 예외를 지정하기

반대로, 특정 예외에 대해서만 재시도를 수행하도록 설정할 수도 있습니다. 이 경우 retryExceptions 속성을 사용합니다.

3.1 retryExceptions 설정

이번에는 TimeoutException이 발생할 때만 재시도를 수행하도록 설정합니다.

resilience4j:
  retry:
    configs:
      default:
        max-attempts: 3
        wait-duration: 100ms
        exponential-backoff:
          enabled: true
          multiplier: 2
        retryExceptions:
          - java.net.SocketTimeoutException
  • retryExceptions: 여기서는 SocketTimeoutException에 대해서만 재시도를 수행하도록 설정했습니다. 이 설정이 적용되면 다른 예외가 발생할 경우 재시도가 수행되지 않습니다.

4. 예외 처리 코드 업데이트

AccountsController에서 TimeoutException을 던져서 재시도가 수행되는지 확인합니다.

4.1 TimeoutException 던지기

@GetMapping("/build-info")
@Retry(name = "getBuildInfo", fallbackMethod = "getBuildInfoFallback")
public String getBuildInfo() throws TimeoutException {
    logger.debug("Invoked getBuildInfo method");
    throw new TimeoutException("Simulated Timeout Exception");
}

public String getBuildInfoFallback(Throwable t) {
    logger.debug("Invoked getBuildInfoFallback method due to: " + t.getMessage());
    return "0.9"; // fallback으로 반환할 버전 정보
}
  • TimeoutException을 던져서, 해당 예외에 대해서만 재시도하는 동작을 확인합니다.

5. Gateway Server에서의 예외 기반 재시도 설정

Gateway Server에서도 특정 예외에 대해 재시도를 수행할 수 있습니다. 예외 기반 재시도를 설정하려면 setExceptions() 메서드를 사용합니다.

5.1 Gateway Server에서 재시도 예외 설정

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("loans", r -> r.path("/loans/**")
            .filters(f -> f
                .retry(config -> config
                    .setRetries(3)
                    .setBackoff(Duration.ofMillis(100), Duration.ofSeconds(1), 2, true)
                    .setExceptions(SocketTimeoutException.class)
                    .setStatuses(HttpStatus.INTERNAL_SERVER_ERROR))
            )
            .uri("lb://loans"))
        .build();
}
  • setExceptions(SocketTimeoutException.class): 이 설정을 통해 SocketTimeoutException이 발생할 때만 재시도를 수행합니다.
  • setStatuses(HttpStatus.INTERNAL_SERVER_ERROR): 특정 HTTP 상태 코드에 대해서도 재시도를 수행할 수 있습니다.

6. Gateway Server 필터 개선

이전에 Gateway Server에서 Retry Pattern을 테스트할 때, 동일한 Correlation ID가 응답 헤더에 여러 번 추가되는 문제가 있었습니다. 이 문제를 해결하기 위해, 조건문을 추가하여 동일한 헤더가 여러 번 추가되지 않도록 수정합니다.

6.1 ResponseTraceFilter 수정

@Component
public class ResponseTraceFilter extends AbstractGatewayFilterFactory<ResponseTraceFilter.Config> {

    public static class Config {
        // Configuration properties (if any) for the filter can go here.
    }

    public ResponseTraceFilter() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                if (!exchange.getResponse().getHeaders().containsKey(CORRELATION_ID_HEADER)) {
                    exchange.getResponse().getHeaders().add(CORRELATION_ID_HEADER, UUID.randomUUID().toString());
                }
            }));
        };
    }
}
  • if (!exchange.getResponse().getHeaders().containsKey(CORRELATION_ID_HEADER)): 이 조건문을 통해 이미 존재하는 헤더를 중복 추가하지 않도록 했습니다.

7. 테스트 및 결과 확인

7.1 애플리케이션 빌드 및 재시작

애플리케이션을 빌드하고 재시작하여 변경 사항을 반영합니다.

./mvnw clean install -DskipTests
java -jar target/accounts-ms.jar
java -jar target/gateway-server.jar

7.2 Postman에서 API 테스트

build-info API를 Postman에서 호출하여 변경된 동작을 확인합니다.

  • NullPointerException: 재시도 없이 즉시 fallback 메서드가 호출됩니다.
  • TimeoutException: 재시도 후 실패 시 fallback 메서드가 호출됩니다.

8. 요약

이번 강의에서는 Retry Pattern을 더욱 정교하게 구현하기 위해 ignoreExceptionsretryExceptions를 활용하는 방법을 배웠습니다. 또한, Gateway Server에서의 예외 기반 재시도 설정과 응답 헤더 중복 문제를 해결하는 방법도 다루었습니다. 이 모든 설정을 통해 마이크로서비스의 안정성과 신뢰성을 크게 향상시킬 수 있습니다.

추가적으로, 이 모든 설정과 개념을 문서화하여 필요한 경우 언제든지 참조할 수 있도록 정리했습니다.

9. 최종 코드

9.1 AccountsController

@RestController
public class AccountsController {

    private static final Logger logger = LoggerFactory.getLogger(AccountsController.class);

    @GetMapping("/build-info")
    @Retry(name = "getBuildInfo", fallbackMethod = "getBuildInfoFallback")
    public String getBuildInfo() throws TimeoutException {
        logger.debug("Invoked getBuildInfo method");
        throw new TimeoutException("Simulated Timeout Exception");
    }

    public String getBuildInfoFallback(Throwable t) {
        logger.debug("Invoked getBuildInfoFallback method due to: " + t.getMessage());
        return "0.9";
    }
}

9.2 application.yml

resilience4j:
  retry:
    configs:
      default:
        max-attempts: 3
        wait-duration: 100ms
        exponential-backoff:
          enabled: true
          multiplier: 2
        retryExceptions:
          - java.net.SocketTimeoutException

이 강의를 통해 Retry Pattern의 다양한 설정과 적용 방법을 익힐 수 있었기를 바랍니다. 다음 강의에서도 중요한 패턴들을 다룰 예정이니 기대해 주세요.

profile
무슨 생각하며 사니

0개의 댓글