백앤드 개발일지

[CircuitBreaker] 서킷브레이커의 이벤트를 슬랙으로 알림받기

giron 2024. 4. 29. 00:54
728x90

서킷브레이커는 회로 차단기로도 불리며 주요 목적은 시스템의 일부분에 문제가 발생했을 때, 그 문제가 전체 시스템으로 확산되는 것을 방지하는 것입니다.

state

가령 게이트웨이에서 라우팅하는 대상의 서버가 응답이 없거나 특정 에러를 계속 발생시킨다면 게이트웨이의 자원도 고갈됩니다. 모든 서비스에 장애가 전파되는 것이지요. 이러한 문제를 막기 위해 회로차단기(circuitBreaker)를 둠으로써 장애의 전파를 막을 수 있습니다.

 

서킷 브레이커는 개발자가 설정하는 값에 따라 어느 상황에서 서킷을 열지를 결정할 수 있습니다. (설정 관련 문서 를 참고해서 작성할 수 있습니다.)

이러한 서킷이 열렸는지 닫혔는지 여부도 개발자가 알수 있어야한다고 생각됩니다. 따라서 서킷의 이벤트가 발생할때 슬랙으로 알림을 전송받도록 구현해보겠습니다.

 

1. 슬랙 준비

(슬랙 연동은 해당 블로그를 참고했습니다.)

슬랙 연동이 완료되면 슬랙의 채널에서 채널에 이 앱 추가를 눌러주어 추가시켜줍니다.

채널에 이 앱 추가 클릭

2. 코드 작성

서킷브레이커는 이벤트를 listen하는 곳을 EventConsumer라고 불립니다. 이벤트를 컨슘할 수 있는 클래스를 작성해줍니다. 

CircuitBreakerEventConsumer를 작성해주고 EventConsumer 인터페이스를 구현해줍니다.

@Component
public class CircuitBreakerEventConsumer implements EventConsumer<CircuitBreakerOnStateTransitionEvent> {
   private static final Logger logger = LoggerFactory.getLogger("CircuitBreakerEventConsumer");
   private final SlackNotificationService slackNotificationService;

   public CircuitBreakerEventConsumer(SlackNotificationService slackNotificationService) {
      this.slackNotificationService = slackNotificationService;
   }

   @Override
   public void consumeEvent(CircuitBreakerOnStateTransitionEvent transitionEvent) {
      if (transitionEvent.getStateTransition() == CLOSED_TO_OPEN) {
         logger.warn("[서킷] 열렸다!!!!!!!!");
         slackNotificationService.send("서킷 브레이커가 열렸습니다: " + transitionEvent.getCircuitBreakerName());
      } 
   }
}

이후 슬랙 알림 서비스를 간단하게 구현해줍니다. 

@Service
public class SlackNotificationService {

   private static final Logger logger = LoggerFactory.getLogger("SlackNotificationService");

   private final WebClient webClient = WebClient.create();

   private final ObjectMapper objectMapper;
   private final String slackWebhookUrl;

   public SlackNotificationService(@Value("${slack.webhook.url}") String slackWebhookUrl, ObjectMapper objectMapper) {
      this.slackWebhookUrl = slackWebhookUrl;
      this.objectMapper = objectMapper;
   }


   public void send(String message){
      Mono<String> resultMono  = sendSlackMessage(message);
      resultMono.doOnSuccess(result -> logger.info("Message sent successfully: " + result))
         .doOnError(error -> logger.error("Error sending message: " + error.getMessage()))
         .subscribe();
   }

   public Mono<String> sendSlackMessage(String message) {
      String jsonMessage = getMessage(message);

      return webClient.post()
         .uri(slackWebhookUrl)
         .bodyValue(jsonMessage)
         .retrieve()
         .bodyToMono(String.class);
   }

   private String getMessage(String message) {
      Map<String, String> messageMap = new HashMap<>();
      messageMap.put("text", message);

      try {
         return objectMapper.writeValueAsString(messageMap);
      } catch (JsonProcessingException e) {
         throw new RuntimeException("JSON 변환 중 오류 발생", e);
      }
   }
}

마지막으로 서킷브레이커의 이벤트를 등록시켜주어야 합니다.

각각의 서킷브레이커마다 다른 이벤트를 적용시킬수도 있고, 모든 서킷브레이커를 같은 이벤트를 consum할 수 있도록 설정할 수 있습니다.

@Configuration
public class CircuitBreakerEventConfig {

   public CircuitBreakerEventConfig(CircuitBreakerRegistry circuitBreakerRegistry, CircuitBreakerEventConsumer circuitBreakerEventConsumer) {

      circuitBreakerRegistry.getAllCircuitBreakers()
         .forEach(it -> it.getEventPublisher().onStateTransition(circuitBreakerEventConsumer));

      // 각각의 서킷마다 이벤트 적용
      // CircuitBreaker circuitBreaker = circuitBreakerRegistry.find("exampleCircuitBreaker")
      //     .orElseThrow(IllegalArgumentException::new);
      //
      // circuitBreaker.getEventPublisher().onStateTransition(circuitBreakerEventConsumer);
   }

}

이후 실제 서킷이 열리거나 닫힌다면..? 정상적으로 어떤 서킷브레이커가 열렸는지 닫혔는지 표시가 됩니다.

슬랙의 알림

Reference

https://resilience4j.readme.io/docs/circuitbreaker#create-and-configure-a-circuitbreaker

728x90