반응형
개념
Circuit Breaker란?
- 하나의 서비스가 실패할 때, 그 서비스로의 트래픽을 차단하여 실패가 확산되는 것을 방지하는 패턴을 말한다.
- 서비스간의 의존성 문제를 해결하고, 전체 시스템의 회복력을 높이기 위해 사용된다.
- 대표적인 라이브러리로는 resilience4j가 있다. (아래 예제도 resilience4j를 활용하여 진행한다.)
Circuit Breaker의 상태
- CLOSED: 서비스 호출이 정상적으로 이루어지는 상태. 모든 요청이 통과된다.
- OPEN: 서비스 호출이 실패하여 트래픽을 차단한 상태. 모든 요청이 즉시 실패로 반환된다.
- HALF-OPEN: 일정 시간이 지난 후, 서비스가 다시 정상적으로 동작하는지 확인하기 위해 일부 요청을 허용하는 상태. 해당 상태에서 요청이 성공하면 CLOSED, 실패하면 OPEN 상태로 전환된다.
예제
build.gradle.kts
dependencyManagement {
imports {
mavenBom("org.springframework.cloud:spring-cloud-dependencies:2023.0.3")
}
}
dependencies {
// for resilience4j
implementation("org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j")
// 아래 의존성 추가하지 않으면 @CircuitBreaker 어노테이션이 동작하지 않음
implementation("org.springframework.boot:spring-boot-starter-aop")
}
application.yml
resilience4j:
circuitbreaker:
configs:
default:
sliding-window-type: count_based # 값을 집계하는 방식
sliding-window-size: 10 # 윈도우 사이즈
failure-rate-threshold: 50 # 실폐율 임계값. 실패율 >= 50%이면 OPEN상태로 전환
minimum-number-of-calls: 5 # 집계를 위한 최소 호출 수
permitted-number-of-calls-in-half-open-state: 2 # HALF_OPEN 상태일 때 허용되는 호출 수
wait-duration-in-open-state: 30s # OPEN -> HALF_OPEN 상태로 전환되는 시간
instances:
message-service: # message-service라는 이름의 인스턴스 등록
base-config: default
MessageService
@Service
class MessageService {
@CircuitBreaker(name = "message-service", fallbackMethod = "fallbackDemo")
fun getMessage(name: String): String {
if (name.isEmpty()) throw IllegalStateException("not supported")
return "success"
}
private fun fallbackDemo(name: String, e: Exception): String {
LoggerFactory.getLogger(this::class.java).warn("fallback. name: {}", name, e)
return "fallback"
}
}
CircuitBreakerTest
@SpringBootTest
class CircuitBreakerTest {
@Autowired
private lateinit var messageService: MessageService
@Autowired
private lateinit var circuitBreakerRegistry: CircuitBreakerRegistry
private lateinit var circuitBreaker: CircuitBreaker
@BeforeEach
fun setup() {
circuitBreaker = circuitBreakerRegistry.circuitBreaker("message-service")
}
@Test
fun `CLOSED 상태에서 5번 연속 실패하면 OPEN 상태로 전환`() {
circuitBreaker.transitionToClosedState()
(1..4).forEach {
assertEquals("fallback", messageService.getMessage(""), "count: $it")
assertEquals(State.CLOSED, circuitBreaker.state, "count: $it")
}
assertEquals("fallback", messageService.getMessage(""), "count: 5")
assertEquals(State.OPEN, circuitBreaker.state, "count: 5")
}
@Test
fun `CLOSED 상태에서 5번 성공 & 5번 실패하면 OPEN 상태로 전환`() {
circuitBreaker.transitionToClosedState()
(1..5).forEach {
assertEquals("success", messageService.getMessage("john"), "count: $it")
assertEquals(State.CLOSED, circuitBreaker.state, "count: $it")
}
(6..9).forEach {
assertEquals("fallback", messageService.getMessage(""), "count: $it")
assertEquals(State.CLOSED, circuitBreaker.state, "count: $it")
}
assertEquals("fallback", messageService.getMessage(""), "count: 10")
assertEquals(State.OPEN, circuitBreaker.state, "count: 10")
}
@Test
fun `CLOSED 상태에서 50번 성공해도 CLOSED 상태 유지`() {
circuitBreaker.transitionToClosedState()
(1..50).forEach {
assertEquals("success", messageService.getMessage("john"), "count: $it")
assertEquals(State.CLOSED, circuitBreaker.state, "count: $it")
}
}
@Test
fun `HALF-OPEN 상태에서 2번 연속 실패하면 OPEN 상태로 전환`() {
circuitBreaker.transitionToOpenState()
circuitBreaker.transitionToHalfOpenState()
assertEquals("fallback", messageService.getMessage(""))
assertEquals(State.HALF_OPEN, circuitBreaker.state)
assertEquals("fallback", messageService.getMessage(""))
assertEquals(State.OPEN, circuitBreaker.state)
}
@Test
fun `HALF-OPEN 상태에서 1번 실패 & 1번 성공하면 OPEN 상태로 전환`() {
circuitBreaker.transitionToOpenState()
circuitBreaker.transitionToHalfOpenState()
assertEquals("fallback", messageService.getMessage(""))
assertEquals(State.HALF_OPEN, circuitBreaker.state)
assertEquals("success", messageService.getMessage("john"))
assertEquals(State.OPEN, circuitBreaker.state)
}
@Test
fun `HALF-OPEN 상태에서 2번 성공하면 CLOSED 상태로 전환`() {
circuitBreaker.transitionToOpenState()
circuitBreaker.transitionToHalfOpenState()
assertEquals("success", messageService.getMessage("john"))
assertEquals(State.HALF_OPEN, circuitBreaker.state)
assertEquals("success", messageService.getMessage("john"))
assertEquals(State.CLOSED, circuitBreaker.state)
}
@Test
fun `OPEN 상태에서 어떤걸 요청해도 fallback 응답`() {
circuitBreaker.transitionToOpenState()
assertEquals("fallback", messageService.getMessage(""))
assertEquals(State.OPEN, circuitBreaker.state)
assertEquals("fallback", messageService.getMessage("john"))
assertEquals(State.OPEN, circuitBreaker.state)
}
}
참고
반응형
'Development > Spring' 카테고리의 다른 글
[Spring] HTTP Interface(RestClient, WebClient) (0) | 2024.11.24 |
---|---|
[Spring] Partitioning (2) | 2024.09.13 |
[Spring] Sharding (0) | 2024.07.12 |
[Spring] multi-module 프로젝트 구성하기(kotlin, gradle) (0) | 2024.07.07 |
[Spring] Exposed (0) | 2024.04.10 |