티스토리 뷰
이 시간 부터는 이제 장애처리에 관련한 내용을 알아보려 합니다. 그 전에 완성된 프로젝트를 공유하도록 하겠습니다.
두 폴더 모두 D: 에 위치시키는 것 을 추천 드립니다.
Kafka는 따로 설치해야 합니다. 관련 글을 참고해주세요.
-Service가 포함된 WorkSpace-
-Service와 관련된 Repo-
[MSA] Apache Kafka 설치
Kafka는 MSA와 굉장히 밀접한 기술입니다. 대부분의 MSA 형태의 애플리케이션에는 Kafka가 들어갑니다. 저희가 흔히 쓰는 Spirng-Cloud-Nefilx 오픈소스를 개발한 Nefilx에서도 사용 중 입니다. 이번 포스팅
ggparkitbank.tistory.com
[MSA] Apache Kafka - Kafka Connect 개념/ MariaDB설치
개요 이번 시간에는 아래와 같은 내용을 배우려 합니다. Kafka Connect의 개념 연동을 위한 MariaDB 설치 #Kafka Connect 란? Kafka Connect는 쉽게 말하면 자유롭게 Data를 Import/Export 해주는 기능입니다. 그..
ggparkitbank.tistory.com
[MSA] Kafka Connect 설치
오늘은 정말 삽질을 많이했다. Kafka Connect를 설치하기 위해서는 confluent 라는 어플리케이션을 받아야합니다. 정석적인 방식은 다음과 같습니다. 저는 이렇게 실행했을때 오류가 발생해 정말 오랜
ggparkitbank.tistory.com
#CircuitBreaker
CircuitBreaker는 쉽게 말하면 장애처리 System입니다.
Circuit Breaker를 통해서 장애가 발생한 서비스에 대해 반복적인 호출이 되지 못하게 차단합니다. 또한 특정 서비스가 정상적으로 동작하지 않을 경우 다른 기능으로 대체 후 수행하도록 하는 장애 회피 역할 또한 있습니다. 예를 들면 TimeOut이 지속적으로 발생하면, 더 이상 호출하지 않고 차단하는 역할등이 있습니다.
#Hystrix? Resilience4j?
기존에는 Hystrix를 사용해서 CircuitBreaker를 사용했습니다. 하지만 Zuul과 마찬가지로, 더 이상의 지원이 없고 Spring 버젼에 따라서 사용할 수 없는 경우가 있음에 따라서 Resilience4j로 대체가 됐습니다. 다만 다양한 정보를 제공하기 위해 Hystrix의 정보도 아래에 포함시켰습니다.
#Hystrix Dependency 정보
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
#Resilience4j
만약 Resilience4j에서 CircuitBreaker만 쓸 예정이라면 아래와 같이 입력해줍니다.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
#상황 설정
실제 실습에 앞서서 먼저 Circuit Breaker가 동작할 수 있는 상황을 제공하려 합니다. 모든 Service를 Down시켜주고,
User-Service와 Gateway-service 그리고 Config-Service 와 Eureka-Servcer(Ecommerce)를 실행시켜줍니다. 그리고 다음의 과정을 진행합니다.
Config-Service 를 가장 먼저 실행시켜 주세요.
회원 등록

로그인

회원정보 확인
Token정보와 URL뒤에 ID값을 붙히는 것 그리고 GET방식으로 요청을 보내는 것을 기억해주세요!

결과를 확인해 보면 500에러가 뜨면서 Feign Client에서 Error메시지가 호출됩니다. 딱 1줄만으로 오류 내용을 알 수 있는데, 현재 Order-Service가 Down되어 있는 상태이기에 값을 호출받지 못해서 해당 오류가 발생했습니다. 그리고 어떻게 보면 이게 정상적으로 출력되는 화면입니다! 저희는 CircuitBreaker를 활용하기 위해서 일부러 오류를 낸 것 이니까요!
#Resilience4J
이제 Resilience4j를 사용하기에 앞서 먼저 Dependency를 추가해줍니다. UserService에 Pom.xml에 추가해주면 됩니다.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
다음으로는 UserServiceImpl.java 에서 다음과 같이 CircuitBreakerFactory를 생성자에 주입시켜줍니다.
#UserServiceImple.java
UserRepository userRepository;
BCryptPasswordEncoder passwordEncoder;
Environment env;
RestTemplate restTemplate;
OrderServiceClient orderServiceClient;
CircuitBreakerFactory circuitBreakerFactory;
@Autowired
public UserServiceImpl(UserRepository userRepository,
BCryptPasswordEncoder passwordEncoder,
Environment env,
RestTemplate restTemplate,
OrderServiceClient orderServiceClient,
CircuitBreakerFactory circuitBreakerFactory){
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.env = env;
this.restTemplate = restTemplate;
this.orderServiceClient = orderServiceClient;
this.circuitBreakerFactory = circuitBreakerFactory;
}
그리고 다음과 같이 코드를 작성해주시면 됩니다. CircuitBreaker를 생성하고, Ramda를 통해 circuitBreaker에서 getOrders(userId)의 유효성을 체크하고 에러가 발생하면, ArrayList를 새로 생성해 Return해 줍니다. 다만 안에 있는 값은 비어있으니 orders: [ ] 형태로 출력 될 예정입니다.
/* ErrorDecoder */
CircuitBreaker circuitBreaker = circuitBreakerFactory.create("circuitbreaker");
List<ResponseOrder> orderList = circuitBreaker.run(()->orderServiceClient.getOrders(userId),
throwable -> new ArrayList<>());
그럼 정말로 그런지 확인해볼까요?
#User Service 재실행 후 TEST
다시 똑같은 과정을 반복해보겠습니다.
회원 등록

로그인

회원정보 확인
과연 다시 Error를 출력할까요?



#Resilience4JConfig
먼저 Config Package 생성후 Resilience4JConfig Class를 생성합니다.

그리고 아래와 같이 작성해줍니다.
package com.example.userservice.config;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
@Configuration
public class Resilience4JConfig {
@Bean
public Customizer<Resilience4JCircuitBreakerFactory> globalCustomConfiguration(){
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.failureRateThreshold(4)
.waitDurationInOpenState(Duration.ofMillis(1000))
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
.slidingWindowSize(2)
.build();
TimeLimiterConfig timeLimiterConfig = TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(4))
.build();
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.timeLimiterConfig(timeLimiterConfig)
.circuitBreakerConfig(circuitBreakerConfig)
.build()
);
}
}
되게 복잡해보이지만, 사실 쉽게 생각하면 CircuitBreaker의 설정을 한다고 생각하면 됩니다. 약간의 주석이 있으니 참고해주세요.
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.failureRateThreshold(4) // CircuitBreaker Open여부 설정하는 Failure Rate
.waitDurationInOpenState(Duration.ofMillis(1000)) // Open상태 유지하는 지속시간
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)//닫힐때 결과 기록 창의 유형 구성
.slidingWindowSize(2)// 해당창의 크기를 설정
.build(); //빌드.
TimeLimiterConfig timeLimiterConfig = TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(4)) // Future Supplier의 Time limit API
.build();
그리고 마지막으로 아래의 코드는 해당 정보들을 바탕으로 CircuitBreaker를 만들고 Return하는 의미입니다.
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.timeLimiterConfig(timeLimiterConfig)
.circuitBreakerConfig(circuitBreakerConfig)
.build()
);
그리고 저도 이부분이 헷갈려서 도대체 어떻게 된것인지 알아봤는데 애초에 해당 Class는 아래의 Annoation이 적용되어 있습니다, 그렇기 때문에 프로젝트 시작과 동시에 해당 정보가 읽어지게 됩니다. 별도로 Service에 있는 항목을 수정하지 않아도 자동적으로 해당 환경 셋팅이 이루어지게 되는 구조였습니다.
@Configuration
CircuitBreakerFactory가 생성자에 주입됨과 동시에 해당 환경정보가 주입되기에, 다른 부분은 수정할 필요가 없음.

#OrderController.java(Order-Service)
다음으로는 OrderController입니다. 실제 CircuitBraker가 동작하는지 확인하기 위해서 OrderContorller에 의도한 에러를 내려고 합니다. @PostMapping("/{userId}/orders") 부분을 아래와 같이 바꿔주세요.
@PostMapping("/{userId}/orders")
public ResponseEntity<ResponseOrder> createOrder(@PathVariable("userId") String userId,
@RequestBody RequestOrder orderDetails){
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
OrderDto orderDto = mapper.map(orderDetails, OrderDto.class);
orderDto.setUserId(userId);
OrderDto createdOrder = orderService.createOrder(orderDto);
ResponseOrder responseOrder = mapper.map(createdOrder,ResponseOrder.class);
// /*kafka*/
// orderDto.setOrderId(UUID.randomUUID().toString());
// orderDto.setTotalPrice(orderDetails.getQty() * orderDetails.getUnitPrice());
//
// /* send this order */
// kafkaProducer.send("example-catalog-topic",orderDto);
// orderProducer.send("orders", orderDto);
//
//
// ResponseOrder responseOrder = mapper.map(orderDto,ResponseOrder.class);
return ResponseEntity.status(HttpStatus.CREATED).body(responseOrder);
}
그리고 Application.yml 파일에 DataSource 부분을 다음과 같이 수정해줍니다.
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb
# url: jdbc:mysql://localhost:3307/mydb
# driver-class-name: org.mariadb.jdbc.Driver
# username: root
# password: test1357
후에 다시 되돌릴 예정이니 주석을 지우시면 안됩니다!
먼저 로그인 과정까지 거쳐주시고, 다음과 같이 Order-Service를 기동시켜 주문을 진행하도록 합니다.

주문정보 확인 시 다음과 같이 정상적으로 주문정보가 출력되는 것을 볼 수 있습니다

다만 저희가 원하는 것은 CircuitBreaker가 정상적으로 작동하는 것을 보고 싶으니 다시 Order-Service를 종료합니다.

에러가 아닌 orders가 비어있는 것을 확인 할 수 있습니다. 이로서 저희가 만든 CircuitBreaker가 정상 작동 한다는 것을 알 수 있습니다.
마치며...
이번 시간에는 CircuitBreaker에 대한 개념과 기본적인 사용방법을 실습을 통해서 알아봤습니다. 알듯말듯 하지만, 갈피는 잡힌 것 같아서 의미가 있었던 것 같습니다.
요약: 장애 발생 시 Error를 출력하는 대신 다른 동작을 통해, 장애처리를 하기위해 CircuitBreaker를 사용해봤다.
감사합니다.
- Total
- Today
- Yesterday
- producer
- 미래의나에게동기부여
- 빅-오
- LoadBalancer
- ACTUATOR
- elasticSearch
- UserService
- consumer
- Logstash to ElasticSearch
- rabbitmq
- prometheus
- kafka
- Spring + ELK
- git
- Gateway
- github
- 루틴기록
- Logstash 활용
- config
- Kafka Connect
- 운동
- docker
- springcloud
- 오늘저녁 삼겹살
- JWT
- 운동일기
- zipkin
- MSA
- MariaDB
- Feign
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |