hystrix:
threadpool:
default:
coreSize: 100 # Hystrix Thread Pool default size
maximumSize: 500 # Hystrix Thread Pool default size
keepAliveTimeMinutes: 1
allowMaximumSizeToDivergeFromCoreSize: true
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000 #설정 시간동안 처리 지연발생시 timeout and 설정한 fallback 로직 수행
circuitBreaker:
requestVolumeThreshold: 2 # 설정수 값만큼 요청이 들어온 경우만 circut open 여부 결정 함
errorThresholdPercentage: 50 # requestVolumn값을 넘는 요청 중 설정 값이상 비율이 에러인 경우 circuit open
enabled: true
HystrixCommand 정의
@Service
public class OrderService {
private CustomerClient customerClient;
public OrderService(CustomerClient customerClient) {this.customerClient = customerClient; }
@HystrixCommand(fallbackMethod = "getDefaultAllCustomer")
public List<Customer> getAllCustomer() { return customerClient.findAll();
}
public List<Customer> getDefaultAllCustomer() {
Customer customer = new Customer();
customer.setCustomerId(Integer.MAX_VALUE);
customer.setName("fallback");
customer.setEmail("fallback@gmail.com");
return Arrays.asList(customer);
}
}
Application annotation 추가
@EnableHystrix
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
@RestController
public class Service01Application {
public static void main(String[] args) {
SpringApplication.run(Service01Application.class, args);
}
@RequestMapping("/test")
public String getTest(){
return "test";
}
}
- FeignClient Hystrix의 fallback class사용
Feign dependency 에 hystrix가 기본 포함 되어 별도 dependency추가 불필요
단, client 서비스가 eureka에 등록되지 않은경우, Ribbon이 해당 서비스를 찾을 수 없어 Ribbon에러가 먼저 발생하여, Hystrix 적용 안됨.
configuration - application.yml 에 추가
feign에서 hystrix 사용 여부를 선택 할 수 있음(hystrix 관련 config 는 모두 동일)
feign:
hystrix:
enabled: true
hystrix:
threadpool:
default:
coreSize: 100 # Hystrix Thread Pool default size
maximumSize: 500 # Hystrix Thread Pool default size
keepAliveTimeMinutes: 1
allowMaximumSizeToDivergeFromCoreSize: true
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 180000 #설정 시간동안 처리 지연발생시 timeout and 설정한 fallback 로직 수행
circuitBreaker:
requestVolumeThreshold: 2 # 설정수 값만큼 요청이 들어온 경우만 circut open 여부 결정 함
errorThresholdPercentage: 50 # requestVolumn값을 넘는 요청 중 설정 값이상 비율이 에러인 경우 circuit open
enabled: true
Feign Hystrix 정의
@RefreshScope
@FeignClient(
name ="${coe.application.customer-service}",
decode404 = true,
fallback = CustomerFallback.class //fallback 클래스 정의
)
public interface CustomerClient {
@RequestMapping(method = RequestMethod.GET, value = API_V1_BASE_PATH + "/customers")
List<Customer> findAll();
}
Fallback class 정의
@Component
public class CustomerFallback implements CustomerClient {
@Override
public List<Customer> findAll() {
return Arrays.asList(new Customer(1,"coe", "coe@mail.com"));
}
}
- Zuul FallbackProvider 등록
Zuul Gateway에 Fallback Provider를 등록한다.
Zuul dependency 에 hystrix가 기본 포함 되어 별도 dependency추가 불필요
1. configuration - application.yml 에 추가(hystrix 관련 config 는 모두 동일)
hystrix:
threadpool:
default:
coreSize: 100 # Hystrix Thread Pool default size
maximumSize: 500 # Hystrix Thread Pool default size
keepAliveTimeMinutes: 1
allowMaximumSizeToDivergeFromCoreSize: true
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000 #설정 시간동안 처리 지연발생시 timeout and 설정한 fallback 로직 수행
circuitBreaker:
requestVolumeThreshold: 2 # 설정수 값만큼 요청이 들어온 경우만 circut open 여부 결정 함
errorThresholdPercentage: 50 # requestVolumn값을 넘는 요청 중 설정 값이상 비율이 에러인 경우 circuit open
enabled: true
routeFallbackProvider 예시
public class ZuulFallbackProvider implements FallbackProvider {
private static final String SERVICE_ID = "serviceId";
private static final String REQUEST_URI = "requestURI";
@Value("${hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds}")
private String hystrixThreadTimeoutMilliseconds;
@Override
public String getRoute() {return "*";}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
if (cause instanceof HystrixTimeoutException) {
return response(HttpStatus.GATEWAY_TIMEOUT, getInvalidParam(cause), getRootCauseMsg(cause));
} else {
return response(HttpStatus.INTERNAL_SERVER_ERROR, getInvalidParam(cause), getRootCauseMsg(cause));
}
}
private ClientHttpResponse response(HttpStatus status, String invalidParam, String rootCauseMsg) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() { return status; }
@Override
public int getRawStatusCode() {return status.value();}
@Override
public String getStatusText() {return status.getReasonPhrase();}
@Override
public void close() { }
@Override
public InputStream getBody() {
HttpStatus status = getStatusCode();
ErrorResponseBodyVO responseBodyVO;
if (status == HttpStatus.GATEWAY_TIMEOUT) {
responseBodyVO = new ErrorResponseBodyVO("Error"
, "Failed to handle the request in given thread time. (" + rootCauseMsg + ")"
, invalidParam);
} else if (status == HttpStatus.INTERNAL_SERVER_ERROR) {
responseBodyVO = new ErrorResponseBodyVO("Error"
, "Service Unavailable. Please try after sometime. (" + rootCauseMsg + ")"
, invalidParam);
} else {
responseBodyVO = new ErrorResponseBodyVO();
}
return new ByteArrayInputStream(responseBodyVO.toJSONString().getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
private String getInvalidParam(final Throwable cause) {
if (cause instanceof HystrixTimeoutException) {
return "hystrix....timeoutInMilliseconds: " + hystrixThreadTimeoutMilliseconds;
} else {
RequestContext context = RequestContext.getCurrentContext();
String serviceId = context.get(SERVICE_ID).toString().toUpperCase();
String requestURI = context.get(REQUEST_URI).toString();
return serviceId + requestURI;
}
}
private String getRootCauseMsg(final Throwable cause) {
return ExceptionUtils.getRootCauseMessage(cause);
}
}
Fallback 처리를 위한 Provider 등록
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class CoeZuulApplication {
public static void main(String[] args) {
SpringApplication.run(CoeZuulApplication.class, args);
}
@Bean
public FallbackProvider zuulFallbackProvider() {
return new ZuulFallbackProvider();
}
}
@EnableHystrixDashboard
@SpringBootApplication
public class HystrixDashboardApplication {
public static void main(String[] args) {springApplication.run(HystrixDashboardApplication.class, args); }
}
Client 서비스의 actuator hystrix stream endpoint 열기 위한 config 추가
turbine:
appConfig: productService,userService # 모니터 원하는 서비스 나열(eureka에 등록되어 있어야 함)
clusterNameExpression: new String('default') #default cluster를 사용하겠다고 정의
Application annotation 추가
@EnableTurbine
@EnableDiscoveryClient
@SpringBootApplication
public class TurbinServiceApplication {
public static void main(String[] args) {SpringApplication.run(TurbinServiceApplication.class, args);}
}
5. Graphite + Grafana
실시간으로 생성되는 Hystrix Stream 데이터를 historical하게 볼수 있도록 함
Metrics를 dropwizard로 보내고 이를 graphite에 저장하는 형식으로 시스템 부하에 대한 모니터링 필요
Graphite: Hystrix Merics 정보를 파일 형태로 저장
Grafana: Graphite의 Metrics 이력을 챠트로 표현해 줌(Graphite외 다른 저장소 사용 가능)
예제에 사용된 Graphite + Grafana 이미지(choopooly/grafana-graphite)
docker run -d -p 8070:80 -p 2003:2003 -p 8125:8125/udp -p 8126:8126 --name grafana-dashboard choopooly/grafana_graphite
Metrics 저장소 상황에 따라 local에 volume 매핑 필요 (기본 /var/lib/graphite/storage/whisper)
Metrics 전송을 위한 dependencies 추가
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-graphite</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>3.1.0</version>
</dependency>
<dependency> <!-- hystrix metrics를 dropwizard로 publishing 하기 위해 필요 -->
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-codahale-metrics-publisher</artifactId>
<version>1.5.12</version>
<exclusions>
<exclusion>
<groupId>com.codahale.metrics</groupId>
<artifactId>metrics-core</artifactId>
</exclusion>
</exclusions>
</dependency>
service에 metrics publish 관련 Bean 등록
@EnableFeignClients
@SpringBootApplication
@EnableHystrix
public class Service01Application {
@Bean
public MetricRegistry metricRegistry() {
return new MetricRegistry();
}
@Bean //metrics 전송 interval 설정 및 초기화
public GraphiteReporter graphiteReporter(MetricRegistry metricRegistry) {
final GraphiteReporter reporter = GraphiteReporter
.forRegistry(metricRegistry)
.build(graphite());
reporter.start(1, TimeUnit.SECONDS);
return reporter;
}
@Bean //Graphite 데이터 취합 endpoints
GraphiteSender graphite() {
return new Graphite(new InetSocketAddress("localhost", 2003));
}
@Bean // Hystrix에 publisher instance 등록
HystrixMetricsPublisher hystrixMetricsPublisher(MetricRegistry metricRegistry) {
HystrixCodaHaleMetricsPublisher publisher = new HystrixCodaHaleMetricsPublisher(metricRegistry);
HystrixPlugins.getInstance().registerMetricsPublisher(publisher);
return publisher;
}
}
Hystrix를 적용한 어플리케이션은 측정 지표 수집에 사용될 /hystrix.stream 종단점을 노출한다.
각 어플리케이션의 API상태 및 Circuit 정보를 Hystrix Dashboard를 통해서 모니터링 할 수 있다. (적용 : 3. Hystrix Dashboard 참고)
Turbine은 다수의 어플리케이션으로 부터 /hystrix.stream 의 데이터를 집계해 하나의 stream으로 통합해준다.
이를 한개의 대시보드 화면에서 조회 할 수 있다. (적용 : 4. Hystrix Turbine 참고)