[스프링 인 액션] 15. 실패와 지연 처리하기

김하영·2021년 8월 19일
0
  • 이 장에서 배우는 내용
    서킷 브레이크 패턴 개요
    Hystrix로 실패와 지연 처리하기
    서킷 브레이커 모니터링
    서킷 브레이커 메트릭 종합하기

15.1 서킷 브레이커 이해하기

서킷 브레이커 패턴 (circuit breaker pattern)

우리가 작성한 코드가 실행에 실패하는 경우 안전하게 처리되도록 한다.
마이크로 서비스에서 더 빛을 발하는 패턴이다.

왜냐하면 한 마이크로 서비스의 실패가 다른 마이크로서비스의 연쇄적인 실패로 확산이 되는 걸 방지해야 하기 때문이다.

문제가 발생할 시 차단기를 내려 전류를 막는 실제 회로 (circuit)과 유사한 형태로,
어떤 이유로 메서드의 실행이 실패하면 (실행횟수나 시간 등 미리 설정해둔 한계값을 초과) 서킷 브레이커가 개방이 되고(차단기를 내리고) 더 이상 실패한 메소드의 호출이 더이상 이뤄지지 않는다.

이때 풀백을 제공해 자체적으로 실패를 처리한다.

  • 서킷브레이커로 보호되고 있는 메서드가 실행에 성공하면(Success) 서킷은 닫힘 상태를 유지한다.
  • 실패할 시 지금 실행되고 있는 실패한 메서드 대신 풀백 메서드가 호출되고 서킷 상태는 열림으로 바뀐다.
  • 때때로 서킷이 절반-열림 상태(half-open)로 바뀌면서 실패했던 메서드의 호출을 서킷 브레이커가 다시 시도한다.
  • 여전히 실패하면 서킷은 다시 열림 상태가 되고, 이후에는 다시 폴백 메서드가 호출된다.
    성공하면 닫힘 상태가 된다.

서킷 브레이커는 메서드 단위로 적용이 된다. 따라서 하나의 마이크로서비스에 많은 서킷 브레이커가 있을 수 있다. 때문에 코드의 어디에 서킷 브레이커를 선언할지 결정할 때는 실패의 대상이 되는 메서드(실패할 위험이 있는)를 식별하는 게 중요하다. 후보로 뽑을 수 있는 메서드이다.

  1. REST를 호출하는 메서드 (원격 서비스로 인해 실패)
  2. 데이터베이스 쿼리를 수행하는 메서드 (데이터베이스가 무반응 상태가 될 수 있거나 스키마 변경으로 인해 실패)
  3. 느리게 실행될 가능성이 있는 메서드 (필시 실패하는 메서드는 아지지만 너무 오랫동안 실행되면 비정상적 상태를 고려)

1과 2는 서킷 브레이커로 실패 처리를 할 수 있지만 마지막 유형은 메서드의 실패보다는 지연(latency)가 문제다.
그러나 이 경우에도 서킷 브레이커의 장점을 살릴 수 있다.
지연은 마이크로서비스 관점에서도 매우 중요한데, 지나치게 느린 메서드가 상위 서비스에 연쇄적인 지연을 유발하면 더 큰 문제가 되기 때문이다.

책에서 사용할 서킷 브레이커 패턴은 NetFlix의 Hystrix로, 자바로 구현이 되었고 대상 메서드가 실패할 때 폴백 메서드를 호출하는 동시에 대상 메서드가 얼마나 실패를 하는지 추적하고, 실패율이 한계값을 초과하면 풀백 메서드를 호출한다.

Hystrix 이름의 유래
Netflix의 개발자들은 회복력, 방어력, 장애 허용 능력 등의 의미를 담은 이름을 원했으며, 결국 Hystrix로 정했다.
서킷브레이커를 선언할 때는 @HystrixCommand 를 선언하고 폴백 메서드만 제공해주면 된다.

15.2 서킷 브레이커 선언하기

pom.xl에 dependency 추가한다.

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

스프링 클라우드 버전 지정한다. ( Eureka, config, hystrix 모두 버전을 설정한다. )

application에 Hystrix 활성화 해준다. (@EnableHystrix)

@SpringBootApplication
@EnableHystrix
public class IngredientServiceApplication {
    ...
}

@HystrixCommand 를 실제 메서드에 사용한다.

@HystrixCommand(fallbackMethod="getDefaultIngredients")
public Iterable<Ingredient> getAllIngredients() {
  ParameterizedTypeReference<List<Ingredient>> stringList =
      new ParameterizedTypeReference<List<Ingredient>>() {};
  return rest.exchange(
      "http://ingredient-service/ingredients", HttpMethod.GET,
      HttpEntity.EMPTY, stringList).getBody();
}

RestTemplate을 사용해서 식자재 서비스로부터 Ingredient 객체들이 저장된 컬렉션을 가져오는 메서드 getAllIngredients에 @HystrixCommand를 적용했다.

실제로 통신을 하는 exchange() 가 호출 실패의 잠재적인 원인이다.
어떠한 이유로 호출이 실패한다면 RestClientException (unchecked, run-time exception)이 발생한다.

RestTemplate이 try-catch를 하지 않았기 때문에 여기서 직접 처리를 해줘야 하는데 현재는 하지 않은 상태로, 이대로 실패한다면 호출 스택의 상위 호출자들에게 계속 에러가 전달이 된다.

계속 전달되는 Unchecked 예외는 골칫거리이고, 특히 조밀하게 연결이 되어있는 마이크로서비스에서 더 심하다.

마이크로서비스에서 생긴 에러는 다른 곳에 전파하지 않고 해당 마이크로서비스안에 남겨야 한다.(= 마이크로서비스의 Vegas Rule)

이때 getAllIngredients에 서킷브레이커를 선언해주면 베가스 규칙은 지킬 수 있다.

HystrixCommand 어노테이션을 선언 후, fallback 메서드를 getDefaultIngredients로 지정한다.

실패할 때 실행이 될 폴백 메서드 만들기

rivate Iterable<Ingredient> getDefaultIngredients() {
  List<Ingredient> ingredients = new ArrayList<>();
  ingredients.add(new Ingredient(
        "FLTO", "Flour Tortilla", Ingredient.Type.WRAP));
  ingredients.add(new Ingredient(
        "GRBF", "Ground Beef", Ingredient.Type.PROTEIN));
  ingredients.add(new Ingredient(
        "CHED", "Shredded Cheddar", Ingredient.Type.CHEESE));
  return ingredients;
}

unchecked 예외가 발생해서 getAllIngredients를 벗어나면 서킷브레이커가 그 예외를 자바아서 getDefaultIngredients를 호출한다.

폴백메서드는 원래의 의도대로 실패한 메서드가 실행이 불가능할 때 대비하는 의도로 만드는 것이 제일 좋다.
폴백 메서드는 원래의 메서드와 시그니처가 같아야한다. 파라메터 종류, 갯수, 리턴값이 같아야 한다.

서킷 브레이커는 연쇄적으로 사용이 가능하다.
단, 폴백 스택의 제일 밑에는 실행에 실패하지 않아 서킷 브레이커가 필요없는 확실한 메서드가 있어야 한다.

15.2.1 지연 시간 줄이기

서킷 브레이커는 메서드의 실행이 끝나고 복귀하는 시간이 너무 오래 걸릴 경우 타임아웃을 사용해 지연시간을 줄일 수 있다.

HystrixCommand를 지정하면 디폴트로 메서드는 1초후에 타임아웃이 되고 폴백메서드가 호출된다.
이유를 불문하고 타임아웃이 되면 무조건 풀백메서드를 호출한다고 보면 된다.

1초의 타임아웃은 대부분의 경우에 맞는 합리적인 값이지만 Hystrix는 commandProperties를 통해 속성을 지정하여 타임아웃을 변경할 수 있다.
commandProperties는 설정될 속성의 이름과 값을 지정하는 하나 이상의 @HystrixProperty 어노테이션을 저장한 배열이다.

어노테이션을 이용해서 다른 어노테이션의 속성을 설정한다는 성격이 다소 특이하다.

@HystrixCommand(
    fallbackMethod="getDefaultIngredients",
    commandProperties={
        @HystrixProperty(
            name="execution.isolation.thread.timeoutInMilliseconds",
            value="500")
    })
public Iterable<Ingredient> getAllIngredients() {
  ...
}
@HystrixCommand(
    fallbackMethod="getDefaultIngredients",
    commandProperties={
        @HystrixProperty(
            name="execution.timeout.enabled",
            value="false")
    })
public Iterable<Ingredient> getAllIngredients() {
  ...
}
  • execution.isolation.tread.timeoutMilliseconds : 타임아웃 속성 설정, 밀리세컨드로 되어있어 타임아웃을 0.5 초로 줄이고자 한다면 500으로 설정하면 된다.
  • execution.timeout.enabled: 타임아웃 설정이 필요없다면 여기를 false로 해주면 된다.
    이처럼 타임아웃을 사용하지 않으면 메서드가 지연되어 10분 30분이 지나도 머물러있기 때문에 연쇄 지연 효과가 발생하므로 주의해야한다.

15.2.2 서킷 브레이커 한계값 관리하기

서킷브레이커로 감싸진 메서드는 기본으로 10초 동안에 20번 이상 호출되고 이 중 50% 이상이 실패하면 열림 상태가 된다.
이후의 모든 호출은 폴백 메서드에 의해 처리되다가 5초후에 half-open 상태가 되어 원래 메서드 호출을 시도한다.

@HystrixCommand(
    fallbackMethod="getDefaultIngredients",
    commandProperties={
        @HystrixProperty(
            name="circuitBreaker.requestVolumeThreshold",
            value="30"),
        @HystrixProperty(
            name="circuitBreaker.errorThresholdPercentage",
            value="25"),
        @HystrixProperty(
            name="metrics.rollingStats.timeInMilliseconds",
            value="20000")
				@HystrixProperty(
            name="circuitBreaker.sleepWindowInMilliseconds",
            value="60000")
    })
public List<Ingredient> getAllIngredients() {
  // ...
}
  1. circuitBreaker.requestVolumeThreshold : 지정된 시간 내에 메서드가 호출되어야하는 횟수
  2. circuitBreaker.errorThresholdPercentage : 지정된 시간 내에 실패한 메서드 호출 비율 (%)
  3. metrics.rollingStats.timeInMilliseconds : 요청 횟수와 에러 비율이 고려되는 시간
  4. circuitBreaker.sleepWinfowInMilliseconds : half-open 상태로 진입하여 실패한 메서드가 다시 시도되기 전에 열림 상태의 서킷이 유지되는 시간

15.3 실패 모니터링하기

서킷 브레이커로 보호되는 메서드가 매번 호출될 때마다 해당 호출에 관한 여러 데이터가 수집되어 Hystrix 스트림으로 발행된다.
앞에서 말한 것처럼 이 스트림은 실행중인 어플리케이션의 상태를 실시간으로 모니터링하는데 사용할 수 있다.
Hystrix 스트림 안에는 아래의 사항들이 포함된다.

  • 메서드가 몇 번 호출되는지
  • 성공적으로 몇 번 호출되는지
  • 풀백 메서드가 몇 번 호출되는지
  • 메서드가 몇 번 타임아웃되는지

요약

  • 서킷 브레이커 패턴은 유연한 실패처리를 할 수 있다.

  • Hystrix는 메서드가 실행에 실패하거나 너무 느릴 때 폴백 처리를 활성화하는 서킷 브레이커 패턴을 구현한다.

  • Hystrix가 제공하는 각 서킷 브레이커는 어플리케이션의 건강 상태를 모니터링할 목적으로 Hystrix 스트림의 메트릭을 발행한다.

  • Hystrix 스트림은 Hystrix 대시보드가 소비할 수 있다. Hystrix 대시보드는 서킷 브레이커 메트릭을 보여주는 웹 어플리케이션이다.

  • Turbine은 여러 애플리케이션의 Hystrix 스트림들을 하나의 Hystrix 스트림으로 종합하며, 종합된 Hystrix 스트림은 Hystrix 대시보드에서 볼 수 있다.
    (우리도 Turbine 서버가 있겠죠? 전에 사용안한다면서 지웠던 기억이..)

profile
Back-end Developer

0개의 댓글