You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 14 Next »

01. Hystrix 사용하기

Display → Product 연동 구간에 Circuit Breaker를 적용!

[display] build.gradle에 hystrix dependency추가

compile('org.springframework.cloud:spring-cloud-starter-netflix-hystrix')

[display] DisplayApplication에 @EnableCircuitBreaker추가

@EnableCircuitBreaker

[display] ProductRemoteServiceImpl에 @HystrixCommand 추가

@HystrixCommand


02. Hystrix Fallback 적용하기

가. Fallback 적용하기

git checkout tags/step-2-hystrix-fallback -b step-2-hystrix-fallback

  • 개요
    • Display 서비스는 외부 Server인 Product API와 연동되어있음
    • Product API에 장애가 나더라도 Display의 다른 서비스는 이상없이 동작하였으면 합니다
    • Product API에 응답 오류인경우, Default값을 넣어 주고 싶습니다


  • [product] ProductController에서 항상 Exception 던지게 수정하기 (장애 상황 흉내)
    • @RestController
      @RequestMapping("/products")
      public class ProductController {
      
          @GetMapping(path = "{productId}")
          public String getProductInfo(@PathVariable String productId) {
              // return "[product id = " + productId + " at " + System.currentTimeMillis() + "]";
              throw new RuntimeException("I/O Exception");
          }
      }
  • [display] ProductRemoteServiceImpl에 Fallback Method 작성하기
    • @Service
      public class ProductRemoteServiceImpl implements ProductRemoteService {
      
          private static final String url = "http://localhost:8082/products/";
          private final RestTemplate restTemplate;
      
          public ProductRemoteServiceImpl(RestTemplate restTemplate) {
              this.restTemplate = restTemplate;
          }
      
          @Override
          @HystrixCommand(fallbackMethod = "getProductInfoFallback")
          public String getProductInfo(String productId) {
              return this.restTemplate.getForObject(url + productId, String.class);
          }
      
          public String getProductInfoFallback(String productId) {
              return "[ this product is sold out ]";
          }
      }

나. Fallback 원인 출력하기

git checkout tags/step-2-hystrix-fallback2 -b step-2-hystrix-fallback2

  • [display] ProductRemoteServiceImp에 Fallback Method 에 Throwable추가
    • @Service
      public class ProductRemoteServiceImpl implements ProductRemoteService {
      
          private static final String url = "http://localhost:8082/products/";
          private final RestTemplate restTemplate;
      
          public ProductRemoteServiceImpl(RestTemplate restTemplate) {
              this.restTemplate = restTemplate;
          }
      
          @Override
          @HystrixCommand(fallbackMethod = "getProductInfoFallback")
          public String getProductInfo(String productId) {
              return this.restTemplate.getForObject(url + productId, String.class);
          }
      
          public String getProductInfoFallback(String productId, Throwable t) {
              System.out.println("t = " + t);
              return "[ this product is sold out ]";
          }
      }
  • Display 호출 확인
    • http://localhost:8081/displays/11111

      2020-02-25 11:32:22.068  INFO 69092 --- [io-8081-exec-10] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
      2020-02-25 11:32:22.068  INFO 69092 --- [io-8081-exec-10] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
      2020-02-25 11:32:22.174  INFO 69092 --- [io-8081-exec-10] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 106 ms
      t = org.springframework.web.client.HttpServerErrorException: 500 null
  • 정리
      • Hystrix에서 Fallback의 실행 여부는 Exception이 발생 했는가 여부
      • Fallback의 정의 여부는 Circuit Breaker Open과 무관.
      • Throwable 파래매터의 추가로. Fallback 원인을 알 수 있다.


03. Hystrix로 Timeout 처리하기

  • [product] ProductController의 throw Exception을 Thread.sleep(2000)로 수정
    • @RestController
      @RequestMapping("/products")
      public class ProductController {
      
          @GetMapping(path = "{productId}")
          public String getProductInfo(@PathVariable String productId) {
              try {
                  Thread.sleep(2000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
      
              return "[product id = " + productId + " at " + System.currentTimeMillis() + "]";
              //throw new RuntimeException("I/O Exception");
          }
      }
  • [display] application.yml 수정하여 Hystrix Timeout 시간 조정하기
    • hystrix:
        command:
          default:    # command key. use 'default' for global setting.
            execution:
              isolation:
                thread:
                  timeoutInMilliseconds: 1000
  • Display → Product 호출 하기

    • 2020-02-25 12:12:54.768  INFO 92144 --- [io-8081-exec-10] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
      2020-02-25 12:12:54.769  INFO 92144 --- [io-8081-exec-10] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
      2020-02-25 12:12:54.857  INFO 92144 --- [io-8081-exec-10] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 88 ms
      t = com.netflix.hystrix.exception.HystrixTimeoutException
  • 정리
      • Hystrix를 통해 실행 되는 모든 메소드는 정해진 응답시간 내에 반환 되어야 한다.
      • 그렇지 못한 경우, Exception이 발생하며, Fallback이 정의된 경우 수행된다.
      • Timeout 시간은 조절할 수 있다. (Circuit 별로 세부 수정 가능하며 뒷 부분에 설명)
      • 언제 유용한가 ?
        • 항상 !!
        • 모든 외부 연동은 최대 응답 시간을 가정할 수 있어야 한다.
        • 여러 연동을 사용하는 경우 최대 응답시간을 직접 Control하는 것은 불가능하다 (다양한 timeout, 다양한 지연등..)




04. Hystrix Circuit Open 테스트


  • [display] application.yml 수정하여 Hystrix 프로퍼티 추가.
    • 10초동안 20개 이상의 호출이 발생 했을때 50% 이상의 호출에서 에러가 발생하면 Circuit Open
    • hystrix:
        command:
          default:    # command key. use 'default' for global setting.
            execution:
              isolation:
                thread:
                  timeoutInMilliseconds: 3000
            circuitBreaker:
              requestVolumeThreshold: 1   # Minimum number of request to calculate circuit breaker's health. default 20
              errorThresholdPercentage: 50 # Error percentage to open circuit. default 50
  • [product] ProductController 다시 수정하여 Exception 던지도록 수정
    • @RestController
      @RequestMapping("/products")
      public class ProductController {
      
          @GetMapping(path = "{productId}")
          public String getProductInfo(@PathVariable String productId) {
      //        try {
      //            Thread.sleep(2000);
      //        } catch (InterruptedException e) {
      //            e.printStackTrace();
      //        }
      //
      //        return "[product id = " + productId + " at " + System.currentTimeMillis() + "]";
              throw new RuntimeException("I/O Exception");
          }
      }


  • Display → Product 호출 확인
    • 2020-02-25 12:38:13.284  INFO 96380 --- [io-8081-exec-10] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
      2020-02-25 12:38:13.285  INFO 96380 --- [io-8081-exec-10] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
      2020-02-25 12:38:13.360  INFO 96380 --- [io-8081-exec-10] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 74 ms
      t = org.springframework.web.client.HttpServerErrorException: 500 null
      t = java.lang.RuntimeException: Hystrix circuit short-circuited and is OPEN
      t = java.lang.RuntimeException: Hystrix circuit short-circuited and is OPEN
      t = java.lang.RuntimeException: Hystrix circuit short-circuited and is OPEN
  • 정리
      • Circuit Open 여부는 통계를 기반으로 한다.
      • 최근 10초간 호출 통계 (metrics.rollingStats.timeInMilliseconds : 10000)
      • 최소 요청 갯수(20) 넘는 경우만 (circuitBreaker.requestVolumeThreshold : 20)
      • 에러 비율 넘는 경우(50%) (circuitBreaker.errorThresholdPercentage : 50)
      • 한번 Circuit이 오픈되면 5초간 호출이 차단되며, 5초 경과후 단 “1개”의 호출을 허용하며 (Half-Open), 이것이 성공하면 Circuit을 다시 CLOSE하고, 여전히 실패하면 Open이 5초 연장된다.
        (circuitBreaker.sleepWindowInMilliseconds : 5000)
  • No labels