MSA
Spring Boot Hystrix 시작하기
strawberry_toast
2020. 2. 26. 21:16
Hystrix
- Netflix가 만든 fault tolerance library
- 장애 전파 방지 & resilience
- Annotation, 상속으로 사용 가능 - 개인 개발자가 쉽게 결정할 수 있음
- 기능적 관점에서 본 4가지 기능
- circuit breaker
- fallback
- thread isolation
- timeout
- Hystrix command를 호출할 때 벌어지는 일
- API caller/callee가 다른 스레드에서 수행됨 (thread isolation)
- Exception 발생 여부를 기록하고 통계를 낸다. 통계에 따라 circuit open 여부를 결정한다. (circuit breaker)
- 실패한 경우 (exception) 사용자가 제공한 메소드를 대신 실행한다. (fallback)
- 특정 시간동안 메소드가 종료되지 않은 경우 exception을 발생시킨다. (timeout)
실습
- 2개의 프로젝트를 생성해 하나는 API caller, 하나는 callee의 역할을 수행하도록 구현
- caller project - sushi, callee project - salad
1. 두 Spring boot WEB 프로젝트 생성
2. application.properties에 server.port를 각각 지정해서 포트가 안겹치게 함
- salad - application.properties
server.port = 8081
- sushi - application.properties
server.port = 8082
3. pom.xml - dependency 설정
- org.springframework.cloud 하위 dependency 적용을 위한 dependencyManagement 설정
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- webflux, hystrix, hystrix-dashboard dependency 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
4. 소스 작성
- (Callee) salad project - SaladConroller
package com.example.salad;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SaladController {
@GetMapping("/salad")
public String getSalad() throws InterruptedException {
return "맛있는 샐러드";
}
}
- (Caller) sushi project - SushiApplication
package com.example.sushi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
@EnableHystrix
@SpringBootApplication
public class SushiApplication {
public static void main(String[] args) {
SpringApplication.run(SushiApplication.class, args);
}
}
- (Caller) sushi project - SushiConroller
package com.example.sushi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SushiController {
@Autowired
private SushiService sushiService;
@GetMapping("/salad")
public String getSalad() {
return sushiService.getSalad();
}
}
- (Caller) sushi project - SushiService
package com.example.sushi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
/*
* Hystrix는 기본적으로 @Component / @Service를 찾고
* 그 안에 있는 @HystrixCommand를 찾아 동작한다.
*/
@Service
public class SushiService {
@Autowired
private WebClient.Builder webClientBuilder;
@HystrixCommand(commandKey = "sushi", fallbackMethod = "getSaladFallback", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"), // set timeout value 1 sec
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "10"), // error 비율이 10% 이상 발생하면 Circuit open
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "2") // 2회 이상 호출되면 통계 시작
})
public String getSalad() {
WebClient webClient = webClientBuilder.baseUrl("http://localhost:8081").build();
//baseURL이 localhost:8081인 webClientBuilder를 가져온다.
return webClient.get() // get 방식으로 가져온다.
.uri("/salad") // baseURL 이후의 URI /salad 이다.
.retrieve() // 클라이언트 메세지를 보낸다.
.bodyToMono(String.class) // body type은 string이다.
.block(); // 가져온 뒤 리턴한다.
}
public String getSaladFallback() {
return "Hello SaladFallback!";
}
}
5. 빌드

- 정상적으로 Callee project의 메소드가 호출되는 것 확인
6. 에러 발생
- (Callee) salad project - SaladConroller에서 오류가 나도록 수정
package com.example.salad;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SaladController {
@GetMapping("/salad")
public String getSalad() throws InterruptedException {
throw new RuntimeException("My I/O Exception");
}
}
- 직접 callee project의 API를 호출하면 에러가 나는 것 확인할 수 있다.

- Caller project의 메소드 호출 시 아래처럼 fallback method가 실행되는 것을 확인할 수 있다.

참고
https://www.youtube.com/watch?v=J-VP0WFEQsY&feature=youtu.be [11번가 Spring Cloud 기반 MSA로의 전환 - 지난 1년간의 이야기]
https://jeong-pro.tistory.com/183 [기본기를 쌓는 정아마추어 코딩블로그]