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 [기본기를 쌓는 정아마추어 코딩블로그]