티스토리 뷰

현재까지 Service Discovery, API Gateway, Configuration Externalization 등을 통해 MSA 수박 겉을 핥아보고 있다. 간단하게 현재까지 이런저런 기능을 구현했었는데 장애가 발생하는 경우에 대해서는 다룬적이 없다. 이렇게 서로 API 통신으로 모든것을 하는데 문제가 발생하면 그걸 어떻게 감지하고 어떻게 처리할까? 이번에도 역시 Neflix에서 제공하는 OSSHystrix를 이용해서 MSA환경에서 어떻게 장애에 대처하는지 알아보도록 하겠다. 

 

Hystrix가 제공하는 핵심 기능은 Circuit Breaker 이다. 이건 회로차단기 라는 뜻인데 회로차단기는 전기 시스템에서 과전류가 발생하면 회로를 차단하여 시스템을 보호하는 기능을 말한다. MSA에서는 API 서버간 통신을 할때 통신하는 서버에 문제가 생긴거 같으면 자동으로 그 서버와 연결을 끊어서 반복적으로 호출하지 않게 하는 기능이라고 생각하면 된다. 그 외의 Hystrix의 기능은 문제 발생시 대체코드를 수행할 수 있게 해주는 Fallback,  API 호출시 Timeout 기능 등이 있다. 

 

예를 들어서 설명을 하자면 API1 이라는 어플리케이션에 Service1 이라는 서비스가 있고 이것이 API2라는 어플리케이션의 Service2라는 서비스를 호출을 한다고 하자. Service1에서 Service2를 호출하는 구조인데 호출할때 시간이 1초 이상 걸리는(Timeout) 횟수를 카운팅 하다가 일정 횟수가 넘어가면 Service1에서 Service2를 호출하는 것을 차단을 한다.(Circuit Breaker) 차단이 되면 Service2를 호출할때는 Service1에서 지정한 다른 메소드를 동작시킬수 있다. (Fallback)

 

Hystrix Circuit Breaker

대략적으로 그림으로 표현하자면 위와 같다. 여기서 timeout 시간, 횟수, 실패확률, 호출 최소값 등을 여러가지 조건을 설정하여 Circuit Breaker를 설정할 수 있다. 이제 어떻게 구현하는지 살펴보도록 하자. 


Hystrix Circuit Breaker 구현방법

기존에 진행했던 모듈을 그대로 재사용을 하겠다. Service Discovery, API Gateway, Spring Config 가 구현되어 있는 상태이고 이를 토대로 어떻게 Circuit Breaker가 동작을 하는지 알아보자. 

 

pom.xml (App1 : EurekaClient1)

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

pom.xml 에는 spring-cloud-starter-neflix-histrix 의존성을 추가시켜준다. 

 

 

Application.java (App1 : EurekaClient1Application.java)

@EnableCircuitBreaker
@EnableEurekaClient
@SpringBootApplication
public class EurekaClient1Application {

    public static void main(String[] args) {
        SpringApplication.run(EurekaClient1Application.class, args);
    }
    
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

springboot main 클래스에는 @EnableCircuitBreaker 를 추가시켜준다. 또한 다른 서비스와 통신을 하기 위해 RestTemplate bean을 생성을 해둔다. 

 

 

그리고 App1에서 App2를 호출하는 메소드를 하나 만들어준다. 기존에 만들어 놓았던 SampleController를 활용한다. 

 

Controller.java (App1 : SampleController.java)

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;

@RefreshScope
@RestController
public class SampleContoller {
	
    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand
    @RequestMapping("/hello")
    public String printHelloWorld() {
		
        String result = restTemplate.getForObject("http://eurekaclient2/hello", String.class);
        return result;
    }
}

여기서 가장 핵심은 @HystirxCommand 이다. 보통은 외부 API 호출하는 메소드에 걸어준다. restTemplate 으로 호출하는 http://eurekaclient2/hello 라는 녀석은 API Gateway를 통해서 EurekaClient2의 /hello 를 호출하고 있는 것이다. 이렇게 하고 실행을 하면 EurekaClient2의 /hello 의 내용이 정상적으로 출력이 될 것이다. 

 

그럼 디버그 모드로 실행을 하고 20 line 쯤 브레이크포인트를 걸어두고 다시 실행을 해보자. 그럼 콘솔에는 

java.util.concurrent.TimeoutException: null
	at com.netflix.hystrix.AbstractCommand.handleTimeoutViaFallback(AbstractCommand.java:997) ~[hystrix-core-1.5.18.jar:1.5.18]
	at com.netflix.hystrix.AbstractCommand.access$500(AbstractCommand.java:60) ~[hystrix-core-1.5.18.jar:1.5.18]
	at com.netflix.hystrix.AbstractCommand$12.call(AbstractCommand.java:609) ~[hystrix-core-1.5.18.jar:1.5.18]
	at com.netflix.hystrix.AbstractCommand$12.call(AbstractCommand.java:601) ~[hystrix-core-1.5.18.jar:1.5.18]

이런 오류가 찍힐거고 브라우저에는 

이렇게 에러페이지를 볼 수 있다. 왜 디버그 모드로 실행을 하면 이렇게 실패를 할까? 

바로 @HystrixCommand 의 1초 timeout 기본옵션 때문이다. printHelloWorld 라는 메소드가 실행된지 1초가 넘으면 위와 같은 오류가 발생을 한다. 디버그 모드로 진입을 하면 1초가 그냥 흘러가기 때문에 이런 오류가 발생한다. 이해를 돕기 위해 이렇게 디버그 모드로 전환하여 실행을 해본것이고 그냥 실행할때 timeout 옵션 등을 설정할 수 있다. 

 

이제는 이런 Hystrix Property 를 어떻게 사용하는지 알아보자. 

@HystrixCommand(
    commandProperties = {
        @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="1")
    }
)
@RequestMapping("/hello")
public String printHelloWorld() {
		
    String result = restTemplate.getForObject("http://eurekaclient2/hello", String.class);
    return result;
}

위와 같은 방식으로 @HystrixCommand를 변형하여 property를 사용할 수 있다. 그중execution.isolation.thread.timeoutInMilliseconds 옵션을 사용한것이고 timeout 시간을 기본값인 1000ms 를 1ms 변경한 것이다. 즉 printHelloWorld() 는 1ms 안에 처리가 되어야지 오류가 아닌걸로 판단한다는 말이다. 현실적으로 이건 거의 불가능에 가깝기게 만약 이렇게 설정하고 이 메소드를 호출해보면 위의 Whitelabel Error Page를 볼수 있을것이다. 1ms 안에 처리가 되지 못해 timeout이 발생한 것이다. 

 

이 상태에서 브라우저 새로고침을 계속 눌러보자. 그럼 어느순간 Whitelabel Error Page가 조금 변화한 것을 확인할 수 있다. 

 

위에는 time-out 에러였는데 이제는 short-circuited 로 변경이 되었다. timeout 실패가 지속되자 Hystrix가 회로를 차단해버린것이다. 이 상태에서는 아예 접근 자체가 안된다고 보면 된다. 몇번이나 실패를 하면 회로차단기를 열지, 접근금지시간은 얼마나 할지 이런 부분들도 다 설정을 할 수 있다. 

@HystrixCommand(
    commandProperties = {
        @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="1"),
        @HystrixProperty(name="circuitBreaker.requestVolumeThreshold", value="10")
    }
)
@RequestMapping("/hello")
public String printHelloWorld() {
		
    String result = restTemplate.getForObject("http://eurekaclient2/hello", String.class);
    return result;
}

이런식으로 다른 옵션을 추가해서 내가 원하는 제어상황을 만들어 낼 수 있다.

더욱 자세한 Hystrix Property 에 대한 자세한 옵션이곳을 참조하길 바란다. 

 

 

어떤 회로차단 상황을 연출할지에 대한 고민이 끝났다면 이제 오류를 발생시켰을때 Fallback 을 구현하는 법에 대해 알아보자. 

 

@HystrixCommand(
    commandProperties = {
        @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="1")
    },
    fallbackMethod = "sampleFallback"
)
@RequestMapping("/hello")
public String printHelloWorld() {
		
    String result = restTemplate.getForObject("http://eurekaclient2/hello", String.class);
    return result;
}

private String sampleFallback() {
    return "circuit breaker on";
}

commandProperties와 마찬가지로 @HystrixCommand 에 인자값으로 fallbackMethod 를 추가해준다. 그리고 fallback시 연결할 메소드를 매핑시킨다. 문제가 발생을 해서 회로차단기가 열리면 다음부터는 /hello에 대한 매핑은 printHelloWorld()로 되는것이 아니고 sampleFallback()으로 된다. 실제로 실행을 해보자. 

 

회로가 차단이 되어도 아까와 같이 Whitelabel Error Page가 나오는것이 아닌 내가 지정한 화면(text)가 나온다. 이런 식으로 간단하게 구현을 할 수 있다. 예를 들기 위해 간단하게 구현했지만 fallback은 이런 로그성 정보를 찍어주는것이 아닌 대체 액션을 정의를 해야 한다. 단순히 로깅만 하려면 fallback 보다는 try-catch로 처리를 해주는 것이 더 낫다. 

 

지금까지 Netflix Hystrix에 대해 알아보았다. 

 

끝!

댓글
최근에 올라온 글
최근에 달린 댓글
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31