모종닷컴

CircuitBreaker 활용(이벤트 리스너 및 수동 조작) 본문

Programming/Spring

CircuitBreaker 활용(이벤트 리스너 및 수동 조작)

모종 2022. 12. 3. 15:01
반응형

이전 포스팅(Circuit Breaker Pattern 그리고 이를 스프링에 적용해보기)에서 예고한 대로 Circuit Breaker의 상태 변화에 따른 이벤트 리스너 추가 및 서킷의 상태를 수동으로 변경하는 법 및 서킷브레이커를 리셋하는 방법을 쓰려고 합니다.

이벤트 리스너 추가

CircuitBreaker를 적용하고 나서 고민했던 포인트 중 하나는 서킷브레이커가 OPEN 상태가 되었을 때 개발자들도 인지를 했으면 좋겠다는 생각이었습니다. 처음에는 서킷브레이커 코드 중에서 상태를 변경하는 부분을 찾아내서 해당 부분을 커스텀하면 되지 않을까?라는 생각을 했었습니다. 서킷브레이커 코드를 조금 찾아보고 나니, 서킷에 이벤트 리스너가 있는 걸 알 수 있었습니다.

관련해서 계속 파고들어 가다 보니 CircuitBreaker.eventPublisher가 있었고 이 부분을 이용하기로 결정하였습니다.

이벤트를 추가하는 방법은 아래와 같이 프로퍼티 파일로부터 생성된 CircuitBreaker를 가져와서 등록된 리스너에게 "특정 이벤트에 대하여 어떻게 리스닝이 동작하면 되는지"를 명시해주면 됩니다.

private val log = KotlinLogging.logger { }

@Configuration
class CircuitBreakerConfig(
    private val circuitBreakerRegistry: CircuitBreakerRegistry
) {
    init {
        circuitBreakerRegistry.circuitBreaker("circuitBreakerTest")
            .eventPublisher.onStateTransition { event ->
                val message =
                    "CircuitBreaker State [${event.stateTransition.fromState} -> ${event.stateTransition.toState}]"
                when (event.stateTransition) {
                    CircuitBreaker.StateTransition.CLOSED_TO_OPEN,
                    CircuitBreaker.StateTransition.HALF_OPEN_TO_OPEN -> {
                        log.error(message)
                    }

                    CircuitBreaker.StateTransition.OPEN_TO_CLOSED,
                    CircuitBreaker.StateTransition.HALF_OPEN_TO_CLOSED,
                    CircuitBreaker.StateTransition.OPEN_TO_HALF_OPEN -> {
                        log.info(message)
                    }

                    else -> log.info(message)
                }
            }
    }
}

이렇게 설정을 하면 서킷브레이커가 특정 상태로 변경될 때 특정 로직을 실행시킬 수 있습니다. 저는 서킷의 상태가 OPEN 되었을 때 에러 로그가 찍히도록 만들었는데 실제로 애플리케이션 구동 후 서킷 상태가 OPEN으로 바뀔 때 아래와 같이 에러 로그가 잘 찍히고 있음을 볼 수 있었습니다.

CircuitBreaker 수동 조작

CircuitBreaker를 사용하면서 어떤 메서드들이 있을까 하고 읽어보면서 유용해 보이는 두 가지를 발견했습니다. 하나는 서킷의 상태를 RESET 시키는 것과 다른 하나는 서킷의 상태를 강제로 바꾸는 거였습니다. 

서킷 상태 Reset 시키기

@RestController
@RequestMapping("/api/circuitbreaker")
class CircuitBreakerTestApiController(
    private val mockService: MockService,
    private val orderService: OrderService,
    private val circuitBreakerRegistry: CircuitBreakerRegistry
) {
    @PostMapping("/{instance}/reset")
    fun reset(@PathVariable instance: String) {
        circuitBreakerRegistry.circuitBreaker(instance).reset()
    }
    ...
 }

이전 포스팅에서 사용하던 Controller를 그대로 사용하였고 reset 시킬 수 있는 엔드포인트를 추가해주었습니다. instance path variable에는 서킷브레이커를 만들 때 사용했던 이름을 넣어주시면 됩니다. 서킷브레이커를 하나만 사용하는 것은 아니기에 이것을 패스 변수로 넘겨받도록 하였습니다. 

테스트

먼저 어플리케이션을 구동시킨 후 실패 요청 10개를 날려서 서킷브레이커의 상태를 OPEN으로 바꿔보도록 하죠. 다음으로 방금 만들어주었던 엔드포인트를 호출해줘 보겠습니다. 그럼 아래와 같이 서킷의 상태 머신에서 가지고 있던 값들이 모두 초기화 상태로 되었음을 볼 수 있습니다.

리셋 전(좌), 리셋 후(우)

상태 강제 변환

CircuitBreaker을 상태로 강제로 변환시켜야 할 때 사용합니다.

@RestController
@RequestMapping("/api/circuitbreaker")
class CircuitBreakerTestApiController(
    private val mockService: MockService,
    private val orderService: OrderService,
    private val circuitBreakerRegistry: CircuitBreakerRegistry
) {
    ...
    @PostMapping("/{instance}/state/{state}")
    fun transitionToStatus(@PathVariable instance: String, @PathVariable state: CircuitBreaker.State) {
        val circuitBreaker = circuitBreakerRegistry.circuitBreaker(instance)
        log.info("transition to $instance status ${circuitBreaker.state} -> $state")
        when (state) {
            CircuitBreaker.State.CLOSED -> circuitBreaker.transitionToClosedState()
            CircuitBreaker.State.DISABLED -> circuitBreaker.transitionToDisabledState()
            CircuitBreaker.State.FORCED_OPEN -> circuitBreaker.transitionToForcedOpenState()
            CircuitBreaker.State.OPEN -> circuitBreaker.transitionToOpenState()
            CircuitBreaker.State.HALF_OPEN -> circuitBreaker.transitionToHalfOpenState()
            CircuitBreaker.State.METRICS_ONLY -> circuitBreaker.transitionToMetricsOnlyState()
        }
    }
    
    ...
}

instance에는 이전과 같이 사용하려는 서킷브레이커의 이름을 넣어주시면 됩니다. state path variable는 변경하려는 상태를 적어주시면 되는데 CircuitBreaker.State에 있는 enum을 적어주시면 됩니다. 

테스트

어플리케이션을 구동시킨 후 현재 상태가 아마 CLOSED일 텐데 위의 엔드포인트를 이용해서 바로 OPEN으로 바꾸면서 actuator 상태를 보도록 하겠습니다. 이전에 적용했던 리스너도 잘 동작하고 상태도 원하는 대로 잘 변경이 되었습니다.

최초 상태(좌), 상태 강제 변환(우)

포스팅 마무리

이번 포스팅에서 CircuitBreaker를 좀 더 효율적으로 활용하기 위한 포인트 몇 개를 소개했습니다. 아직은 사용해 본 지 얼마 안 돼서 일부만 활용하는 모습인데 계속 사용하면서 이슈나 추가적인 활용 포인트가 생긴다면 계속 포스팅을 올려놓도록 하겠습니다. 추가로 resilience4j에 CircuitBreaker 외에도 Bulkhead, RateLimiter, TimeLimiter 등 괜찮아보이는 라이브러리가 존재하는데 시간이 될 때 한 번씩 이 라이브러리에 대한 소개와 적용 법도 포스팅해보도록 하겠습니다.

반응형