마이크로서비스라는 것은 하나의 단일 어플리케이션이 아니라 여러개의 서비스로 개발되다 보니
각각의 서비스에 문제가 생겼을 때, 어떻게 처리를 해야하는지 어떤 서비스가 문제가 생겼고, 해당하는 마이크로서비스의 시작점이 어디고 끝났을 때 반환값을 누구에게 줘야하는지에 대해서 전체적인 흐름을 추적하는 것도 중요하다.
user-service에서 실행한 getUser()에 대해서 user-service가 아니라 order-service나 catalog-service 같은 다른 마이크로서비스에서 오류가 발생했을 때, user-service의 문제가 아님에도 불구하고 user-service에 500번 Error가 종종 발생할 수 있다.
이러한 경우 이걸 해결하기 위해서는 해당하는 마이크로서비스로 요청을 전달하지 않아야 한다.
Feign Client 측에서는 임시로 에러가 발생했을 때 그 에러를 대신할 수 있는 default 값이라던가 우회할 수 있는 또는 정상적인 데이터 값으로 보여줄 수 있는 다른 값을 보여주는 것이 user-service에 준비가 되어 있어야한다.
order-service나 catalog-service에 문제가 생겼다더라도 user-service에서는 문제가 없던것 처럼 정상적인 데이터를 보여줄 수 있게끔 준비를 해야한다.
문제가 생겼던 서비스를 더이상 사용하지 않도록 막아주고 문제가 생겼던 서비스가 정상적으로 복구가 된다고 하면, 이전에 사용했던 것 처럼 정상적인 흐름으로 바꿔주는 장치
중간에 circuit breaker를 유치를 시키고 만약에 supplier 측에서 어떤 문제가 생겼다, connection적인 문제라던가 어떤 Exception이 발생해서 예외상황이 발생했다고 가정 이런 timeout이나 error를 지속적으로 자주 만나게 되면 더이상 해당하는 supplier에 데이터를 전달하지 않고 중간에 있는 circuit breaker가 client 요청을 알아서 회피 시켜줄 수 있는 경우를 얘기한다.
Circuit Breaker는 2가지 경우가 있다.
Open
: MicroService에 문제가 있어 해당 서비스를 사용할 수 없음.
-> 장애 회피(다른 기능으로 대체 수행)
Closed
: 정상적으로 다른 MicroService를 사용할 수 있다.
2019년도 까지는 Spring Cloud Bestflix Hystrix
라이브러리를 통해 CircuitBreaker를 구현을 했으나 2019년도 이후로는 더이상 개발하지 않고 유지보수만 하고 있는 상황인지라 Resilience4j
라는 라이브러리로 대체해서 사용한다.
경량으로 Netflix의 Hystrix를 기반으로 해서 fault tolerance 작업을할 수 있다.
fault tolenrance?
: 에러가 발생을 했을때, 에러가 발생을 한다고 하더라도 정상적인 서비스 처럼 가용할 수 있는 라이브러리다.
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-circuitbreaker-resilience4j'
@Autowired
CircuitBreakerFactory circuitBreakerFactory;
/* ErrorDecoder 사용 */
// List<ResponseOrder> orderList = orderServiceClient.getOrders(userId);
/* CircuitBreaker */
CircuitBreaker circuitbreaker = circuitBreakerFactory.create("circuitbreaker");
// 만약 호출 중에 예외가 발생하면, throwable -> new ArrayList<>() 함수를 호출하여 빈 주문 목록을 반환한다.
List<ResponseOrder> orderList = circuitbreaker.run(() -> orderServiceClient.getOrders(userId),
throwable -> new ArrayList<>());
userDto.setOrders(orderList);
@Configuration
public class Resilience4JConfig {
// CircuitBreaker, TimeLimiter 구성 커스터마이징
@Bean
public Customizer<Resilience4JCircuitBreakerFactory> globalCustomConfiguration() {
// CircuitBreaker가 구성 설정
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
// CircuitBreaker를 열지 결정하는 실패율 (default: 50)
.failureRateThreshold(4)
// CircuitBreaker를 open한 상태를 유지하는 지속기간
// 이 기간 이후에 half-open 상태
// default: 60seconds
.waitDurationInOpenState(Duration.ofMillis(1000))
// CircuitBreaker가 닫힐 때 통화 결과를 기록하는 데 사용되는 슬라이딩 창의 유형을 구성
// 카운트 기반 또는 시간 기반
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
// CirbuitBreaker가 닫힐 때 호출 결과를 기록하는데 사용되는 슬라이딩 창의 크기를 구성
// default: 100
.slidingWindowType(2)
.build();
// TimeLimiter 구성설정
TimeLimiterConfig timeLimiterConfig = TimeLimiterConfig.custom()
// 호출 시간 제한 설정
.timeoutDuration(Duration.ofSeconds(4))
.build();
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.timeLimiterConfig(timeLimiterConfig)
.circuitBreakerConfig(circuitBreakerConfig)
.build());
}
}
스프링 부트 애플리케이션을 Zipkin과 연동
요청 값에 따른 Trace ID, Span ID 부여
Trace와 Span Ids를 로그에 추가 가능
마이크로서비스라는 것은 여러개의 쪼개져있는 서비스들 간에 필요한 데이터 통신을 끊임없이 하고 있다. 그러다보니 중간에 문제가 생겼을 경우 작동이 되지 않는 서비스가 발생할 수 있다.
마이크로서비스 상태값을 호출을 해서 누가 누구를 호출을 했고, 시간이 얼마나 걸렸으며 정상 상태인지 비정상 상태인지 알려주고 시각화 시켜주는 도구로써 Sleuth와 Zipkin을 사용한다고 보면 된다.
curl -sSL https://zipkin.io/quickstart.sh | bash -s
윈도우 10 같은 경우 curl 명령어 사용이 가능하다.
java -jar zipkin.jar
http://localhost:9411/zipkin/
implementation group: 'org.springframework.cloud', name: 'spring-cloud-sleuth'
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-zipkin', version: '2.2.3.RELEASE'
spring:
application:
name: user-service
zipkin:
base-url: http://localhost:9411
enabled: true
sleuth:
sampler:
probability: 1.0
TraceID는 같지만 SpanID는 다름을 볼 수 있다.
Zipkin 페이지 오른쪽 상단에 TraceID를 넣고 검색하면 TraceID 해당하는 분산 추적이 가능하다.