관리 메뉴

피터의 개발이야기

[Kotlin] Resilience4j로 서킷브레이커 패턴 구현하기 본문

Programming/Kotlin

[Kotlin] Resilience4j로 서킷브레이커 패턴 구현하기

기록하는 백앤드개발자 2024. 7. 24. 10:10
반응형

ㅁ 들어가며

 Resilience4j는 마이크로서비스 아키텍처에서 내결함성을 제공하는 라이브러리로, 서킷브레이커 패턴을 쉽게 구현할 수 있게 도와준다. 

여기서는 Resilience4j를 사용하여 서킷브레이커 패턴을 구현하는 방법을 예제 코드와 함께 설명한다.

 

ㅁ 서킷브레이커 패턴이란?

ㅇ 서킷브레이커 패턴은 시스템의 일부에서 장애가 발생했을 때, 그 장애가 전체 시스템으로 확산되는 것을 방지하기 위한 패턴이다.

 

ㅁ 서킷브레이커의 세 가지 상태

닫힘(CLOSED): 정상 상태로, 모든 요청이 통과한다.

열림(OPEN): 장애 상태로, 모든 요청이 즉시 실패한다.

반개방(HALF-OPEN): 테스트 상태로, 일부 요청만 통과하여 성공 여부를 확인한다.

 

ㅁ Resilience4j 설정

dependencies {
    implementation("io.github.resilience4j:resilience4j-circuitbreaker:2.0.0")
}

ㅇ build.gradle.kts에 Resilience4j 의존성을 추가

 

import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.time.Duration

@Configuration
class CircuitBreakerConfiguration {

    @Bean
    fun circuitBreakerRegistry(): CircuitBreakerRegistry {
        return CircuitBreakerRegistry.of(configurationCircuitBreaker())
    }

    private fun configurationCircuitBreaker(): CircuitBreakerConfig {
        return CircuitBreakerConfig.custom()
            .failureRateThreshold(40) // 실패율 임계값
            .waitDurationInOpenState(Duration.ofMillis(10000)) // Open -> Half-Open으로 전환되기 전 대기 시간
            .permittedNumberOfCallsInHalfOpenState(3) // Half-Open 상태에서 허용되는 호출 수
            .slidingWindowSize(10) // 슬라이딩 윈도우 크기
            .recordExceptions(RuntimeException::class.java) // 실패로 기록될 예외 목록
            .build()
    }
}

ㅇ CircuitBreakerConfiguration.kt 서킷브레이커 설정

 

ㅁ 서킷브레이커 사용

import io.github.resilience4j.circuitbreaker.CircuitBreaker
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry
import io.github.resilience4j.circuitbreaker.CallNotPermittedException
import io.vavr.control.Try
import org.springframework.stereotype.Service
import java.util.function.Supplier

@Service
class CallAServerService(
    private val apiClient: CallSomeApiClient,
    private val circuitBreakerRegistry: CircuitBreakerRegistry
) {

    fun callAServer(): String? {
        val circuitBreaker = circuitBreakerRegistry.circuitBreaker("callA")
        val decorateSupplier: Supplier<String> = CircuitBreaker.decorateSupplier(circuitBreaker) {
            apiClient.callAServerApi()
        }

        return try {
            Try.ofSupplier(decorateSupplier)
                .recover { throwable -> callFallback() }
                .get()
        } catch (e: CallNotPermittedException) {
            "Service is blocked because CircuitBreaker blocked this request"
        } catch (e: Exception) {
            "Unknown exception occurred"
        }
    }

    private fun callFallback(): String {
        return "Fallback method running"
    }
}

ㅇ 서킷브레이커를 사용하여 API 호출을 감싸는 CallAServerService 클래스를 작성한다.

 

ㅁ API 클라이언트

import org.springframework.stereotype.Component

@Component
class CallSomeApiClient {
    fun callAServerApi(): String {
        // 실제 API 호출 로직
        return "API response"
    }
}

ㅇ API 호출을 담당하는 CallSomeApiClient 인터페이스를 정의한다.

 

ㅁ 테스트 코드

import io.github.resilience4j.circuitbreaker.CircuitBreaker
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.mockito.Mockito.*

class CallAServerServiceTest {

    private val apiClientMock = mock(CallSomeApiClient::class.java)
    private val circuitBreakerRegistryMock = mock(CircuitBreakerRegistry::class.java)
    private val circuitBreakerMock = mock(CircuitBreaker::class.java)
    private val callAServerService = CallAServerService(apiClientMock, circuitBreakerRegistryMock)

    @Test
    fun `반개방 상태 테스트`() {
        `when`(circuitBreakerRegistryMock.circuitBreaker(anyString())).thenReturn(circuitBreakerMock)
        `when`(apiClientMock.callAServerApi()).thenThrow(RuntimeException::class.java)

        for (i in 1..5) {
            callAServerService.callAServer()
        }

        Thread.sleep(1000)

        assertEquals(CircuitBreaker.State.HALF_OPEN, circuitBreakerMock.state)
    }
}

ㅇ 서킷브레이커의 상태 변화를 테스트하는 코드를 작성한다.
ㅇ 이 테스트 코드는 서킷브레이커가 반개방 상태로 전환되는지 확인한다.

 

ㅁ 마무리

ㅇ Resilience4j를 사용하면 서킷브레이커 패턴을 쉽게 구현할 수 있다.

ㅇ 이를 통해 시스템의 내결함성을 높이고, 장애가 발생했을 때 전체 시스템으로 확산되는 것을 방지할 수 있다.

 

ㅁ 함께 보면 좋은 사이트

resilience4j 로 알아보는 서킷브레이커패턴(CircuitBreaker)

Protecting your Kotlin microservices with Circuit Breaker pattern

반응형
Comments