Hystrix 란,
모노리틱 아키텍쳐가 아닌, MAS 를 따를 때, 각 서버에 대한 액세스 지점을 격리하여 하나의 서버에서 발생한 장애가 다른 서버에 영향을 주는 것 ( 계단식 장애 전파 ) 을 방지와 복원하기 위한 위한 여러 기능을 제공해주는 지연 및 장애 내성 (latency and fault tolerance) 라이브러리이다.
github.com/Netflix/Hystrix/wiki
지연 및 장애 내성 ( Latency and Fault Tolerance )
- 장애가 전파 방지
- Fallback 과 graceful degradation.
- 빠르고 신속한 복구
- circuit breaker를 이용한 Thread and semaphore를 격리
Realtime Operations
- 실시간 모니터링 및 configuration 변경
- 오류 감시 서비스와 property 변경 즉시 적용
- 경고를 받고, 결정을 내리고, 변화에 영향을 미치며, 몇 초 안에 결과를 확인할 수 있음
Concurrency
- Parallel execution.
- Concurrency aware request caching.
- Automated batching through request collapsing.
fallback 메소드와 circuit breaker 사용해보기
사용자 UI 구성을 위한 Display 서버에서 Product 정보를 가져오는 API는 Product 서버로 분리했다 가정해보자,
이 때, Product 서버에서 장애가 발생하면, Display 서버에도 그 장애가 전파가 되게 된다. 가령 응답 받지 못한 요청에 대한 요청을 기다리는 데에 모든 스레드가 할당되어버릴 수도 있다.
이러한 장애 전파 방지를 위해
build.gradle
compile('org.springframework.cloud:spring-cloud-starter-netflix-hystrix')
compile('org.springframework.cloud:spring-cloud-starter-netflix-ribbon')
application.yml
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000 # default 1000
circuitBreaker:
requestVolumeThreshold: 10 # default 20
errorThresholdPercentage: 50 # default 50
getProducts:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000
circuitBreaker:
requestVolumeThreshold: 10 # default 20
errorThresholdPercentage: 50 # default 50
- hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
요청에 대한 Time Out 설정
기본 값이 1초라 요청이 오래 걸리는 경우 반드시 수정이 필요 - hystrix.command.circuitBreaker.requestVolumeThreshold
오류 감시 요청 수 (20 개라면 20 개의 요청에 대한 오류를 감시)
- hystrix.command.circuitBreaker.errorThresholdPercentage
감시한 요청 개수 (requestVolumeThreshold) 중 해당 퍼센테이지 이상의 호출에서 오류가 발생할 경우 circuit 오픈
Display
DisplayApplication.java
@EnableCircuitBreaker
을 추가한다.
@EnableCircuitBreaker
@SpringBootApplication
public class DisplayApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(DisplayApplication.class);
}
}
DisplayController.java
@RestController
@RequestMapping(path = "/displays")
public class DisplayController {
private final ProductRemoteService productRemoteService;
public DisplayController(ProductRemoteService productRemoteService) {
this.productRemoteService = productRemoteService;
}
@GetMapping(path = "/{displayId}")
public String getDisplayDetail(@PathVariable String displayId) {
String productInfo = getProductInfo();
return String.format("[display id = %s at %s %s ]", displayId, System.currentTimeMillis(), productInfo);
}
private String getProductInfo() {
return productRemoteService.getProductInfo("12345");
}
}
ProductRemoteServiceImpl 에서 API 호출하는 메소드에
@HystrixCommand(fallbackMethod = "getProductInfoFallback")
애노테이션을 통해 fallback 메소드를 지정한다.
@Service
public class ProductRemoteServiceImpl implements ProductRemoteService {
private static final String url = "http://localhost:8083/products/";
private final RestTemplate restTemplate;
public ProductRemoteServiceImpl(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Override
@HystrixCommand(fallbackMethod = "getProductInfoFallback")/*(commandKey = )*/
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]";
}
}
Product
ProductController.java
ProductController에서 임의로 exception을 발생시켜 보자.
@RestController
@RequestMapping("/products")
public class ProductController {
@GetMapping(path = "{productId}")
public String getProductInfo(@PathVariable String productId) {
throw new RuntimeException("I/O Exception");
}
}
테스트를 위해 requestVolumeThreshold 를 1 로 지정해놓고 요청을 해본 결과,
단 한번의 exception 이 발생하고, (requestVolumeThreshold 를 1로 설정했기 때문) 서킷이 바로 오픈되었음을 알 수 있다.
실제 요청 응답으로는 Product에서 발생한 에러를 전달받는 것이 아니라, Display 의 fallback 메소드에 정의한 결과가 리턴되는 것을 확인할 수 있다.
Ribbon 란,
spring.io/guides/gs/client-side-load-balancing/
Client Side에서 여러 서버를 라운드로빈 방식으로 Load Balancing 해준다.
build.gradle
compile('org.springframework.retry:spring-retry:1.2.2.RELEASE') // spring cloud requires spring-retry for auto-retry
compile('org.springframework.cloud:spring-cloud-starter-netflix-ribbon')
application.yml
ribbon:
product:
ribbon:
listOfServers: localhost:8082,localhost:7777
MaxAutoRetries 와 MaxAutoRetriesNextServer 를 설정하여, 하나의 서버에서 에러가 발생했을 경우, 다른 서버로 재시도 할 수 있다.
Display
DisplayApplication.java
RestTemplate 빈 정의 시 @LoadBalanced 추가
@SpringBootApplication
@EnableCircuitBreaker
public class DisplayApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(DisplayApplication.class);
}
}
DisplayServiceImpl.java
server url 을 설정 파일에서 정의한 product 로 변경해준다.
@Service
public class ProductRemoteServiceImpl implements ProductRemoteService {
private static final String url = "http://product/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 ]";
}
}
실행 시, 설정파일에 정의되어 있는 서버 리스트로 요청을 변경하여 호출하는 것을 알 수 있다.
DynamicServerListLoadBalancer:{NFLoadBalancer:name=product,current list of Servers=[localhost:8084, localhost:7777],Load balancer stats=Zone stats: {unknown=[Zone:unknown; Instance count:2; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
restTemplate 는 LoadBalancerInterceptor 가 추가된 것을 확인할 수 있다.
application.yml 파일에 retry 옵션을 설정해주면
- MaxAutoRetries – 동일한 서버에서 실패한 요청을 재시도하는 횟수 (default 0)
- MaxAutoRetriesNextServer – 현재 서버를 제외하고 시도할 서버 수 (default 0)
- retryableStatusCodes – 재시도할 HTTP 상태 코드 목록 (500, 503, ...)
application.yml
ribbon:
product:
ribbon:
listOfServers: localhost:8082,localhost:7777
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 1
실행 시, 7777 서버에서 실패할 경우, 8082 서버로 재시도 한다. RetryLoadBalancerInterceptor 추가로, policy에 맞게 서버를 선택하여 요청을 재시도 한다.
'Spring > Spring Cloud' 카테고리의 다른 글
Zuul - API Gateway (0) | 2021.01.17 |
---|---|
Feign (0) | 2021.01.17 |
Eureka (0) | 2021.01.17 |