Spring Cloud

1. hystrix - [Spring Cloud를 활용한 MSA 기초 온라인 강의 실습]

Netflix에서 Circuit Breaker Pattern을 구현한 라이브러리이다. Micro Service Architecture에서 장애 전파 방지를 할 수 있다.

마이크로 서비스 아키텍처에서 분산된 서비스간 통신이 원활하지 않은 경우에 각 서비스가 장애 내성 지연 내성을 갖게하도록 도와주는 라이브러리다.

출처: https://supawer0728.github.io/2018/03/11/Spring-Cloud-Hystrix/

장애 연쇄

출처 : https://cloud.spring.io/spring-cloud-netflix/multi/multi__circuit_breaker_hystrix_clients.html

위 그림에서 supplier 서버에 장애가 생겨 항상 Timeout이 발생하는 경우, supplier 서버를 호출한 client 서버는 Timeout이 발생할 때까지 응답이 밀리게 된다. 응답이 밀리는 동안 요청이 계속 쌓여 결국 client 서버까지 요청이 과하게 밀려 장애가 발생할 수 있다.

이러한 상황이 발생하지 않도록 circuit breaker를 두어 장애 전파를 막을 수 있다.

 

Hystrix Flow Chart

출처 : https://github.com/Netflix/Hystrix/wiki/How-it-Works

  1. HystrixCommand, HystrixObservableCommand 객체 생성 -  Hystrix 명령 적용
    • HystrixCommand, HystrixObservableCommand는 내부적으로 옵저버 패턴을 이용
    • HystrixCommand와 HystrixObservableCommand 차이는 blocking과 non-blocking의 차이
  2. Command 실행
    • HystrixCommand 구현체에서는 execute()와 queue()를 사용할 수 있으며, 차이는 동기/비동기
    • HystrixObservableCommand에서는 observe()와 toObervable()를 사용할 수 있으며, 역시 동기/비동기
    • 차트에서도 보이듯이 모든 실행 메소드를 실행해도 결국 toObservable() 메소드를 호출
    • Command에 대한 스레드를 생성해서 비동기적으로 실행시켰고, 응답에 대해 Fallback 처리할 수 있도록 구독
  3. 캐시 여부 확인
    • 해당 커맨드에 캐시를 사용하고 있고, 캐시에 Response가 있다면 Observable 형태로 즉시 반환
  4. 회로 상태 확인
    • 커맨드를 실행할때, circuit이 열렸는지(or tripped) 확인을 하고, 열려있다면 커맨드를 실행하지 않고 즉시 getFallback()을 실행
  5. 사용가능한 Thread Pool/Queue/Semaphore가 있는지 확인
    • 커맨드의 스레드풀(or 세마포어)과 큐가 가득찼다면, 커맨드를 실행하지 않고 즉시 getFallback() 실행
    • circuit-breaker가 발생한 상황이나, construct() 혹은 run() 발생에서 실패가 된 경우에는 사용자가 구현한 getFallback() 또는 resumeWithFallback() 을 실행
  6. HystrixObservableCommand.construc(), 혹은 HystrixCommand.run() 실행
  7. 회로 상태 연산(Calculate circuit health)
  8. fallback 실행
  9. 응답 반환

Hystrix Circuit Breaker 구현

출처 : https://github.com/Netflix/Hystrix/wiki/How-it-Works

  1. circuit health check를 위한 최소한의 요청이 있을 때(HystrixCommandProperties.circuitBreakerRequestVolumeThreshold())
  2. 그리고, 지정한 오류율을 초과했을 때(HystrixCommandProperties.circuitBreakerErrorThresholdPercentage())
  3. 회로의 상태를 CLOSED에서 OPEN으로 변경
  4. 회로가 열린 동안, 모든 요청에 대해서 fallback method을 바로 실행
  5. 일정 시간이 지난 후(HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()), 하나의 요청을 원래 method로 실행(HALF OPEN). 이 요청이 실패한다면 OPEN으로 두고, 이 요청이 성공한다면 CLOSED로 상태를 변경. 다시 1번으로 돌아감.

기본 설정

  • metrics.rollingStats.timeInMilliseconds : 오류 감시 시간, 기본값 10초
  • circuitBreaker.requestVolumeThreshold : 감시 시간 내 요청 수, 기본값 20
  • circuitBreaker.errorThresholdPercentage : 요청 대비 오류율, 기본값 50

감시시간 내(30초)에, 20번 이상의 요청이 있었고, 그 중에서 오류율이 50% 이상일 때 Circuit Breaker가 작동한다(circuit open)
감시 시간 내에 요청이 반드시 20번 이상이 있어야 회로가 열림. 30초 동안 요청이 19번이었고 모두 실패했어도 Circuit Breaker는 작동하지 않는다.

 

실습

1. 의존성

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

dependencies {
    compile('org.springframework.cloud:spring-cloud-starter-netflix-hystrix') // 1. To use spring-cloud-hystrix
	...
}

2. yml을 이용한 설정

hystrix:
  command:
    productInfo:    # 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

3. command 객체별 개별 설정

class UserResource {
    @HystrixCommand(commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "500")
        },
        threadPoolProperties = {
            @HystrixProperty(name = "coreSize", value = "30"),
            @HystrixProperty(name = "maxQueueSize", value = "101"),
            @HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
            @HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"),
            @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"),
            @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1440")})
    public User getUserById(String id) {
        return userResource.getUserById(id);
    }
}

3. 기본 설정 적용

@SpringBootApplication
@EnableCircuitBreaker
public class DisplayApplication {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(DisplayApplication.class);
    }

}

4. command 객체 설정

@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(commandKey = "productInfo", 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 ]";
    }
}

5. 테스트

@RestController
@RequestMapping("/products")
public class ProductController {

    @GetMapping(path = "{productId}")
    public String getProductInfo(@PathVariable String productId) {
        return "[product id = " + productId + " at " + System.currentTimeMillis() + "]";

//        타임아웃 테스트
//        try {
//            Thread.sleep(2000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }

//      익셉션 테스트
//      throw new RuntimeException("I/O Exception");
    }
}

https://github.com/whdals7337/spring-cloud-workshop

 

whdals7337/spring-cloud-workshop

SK planet Tacademy - Spring Cloud Workshop 강의 실습 및 추가 개발 - whdals7337/spring-cloud-workshop

github.com

 

Reference

https://www.youtube.com/watch?v=iHHuYGdG_Yk 

https://supawer0728.github.io/2018/03/11/Spring-Cloud-Hystrix/