서비스 간 호출 체인이 존재할 때, 하나의 서비스가 장애를 일으키면 의존하는 다른 서비스까지 영향을 받을 수 있다. 이러한 상황을 방지하기 위해 융단기 (Circuit Breaker) 기법이 사용된다. 이는 전기 회로의 차단기와 유사하게, 하위 서비스의 불안정 상태를 감지하고 일시적으로 요청을 차단함으로써 전체 시스템의 과부하를 막는다.
융단기 동작 원리
융단기는 일반적으로 세 가지 상태를 갖는다:
- 닫힘 (Closed): 정상 작동 중. 요청을 처리하며 성공률과 실패율을 모니터링한다.
- 열림 (Open): 지정된 조건(예: 특정 비율 이상의 실패)을 초과하면 요청을 차단한다. 이후 일정 시간 후에 일부 요청을 허용해 서비스의 재개 여부를 확인한다.
- 반열림 (Half-Open): 잠시 후, 일부 요청만 허용하여 서비스가 회복되었는지 테스트한다. 성공 시 닫힘 상태로 전환, 실패 시 다시 열림 상태 유지.
Kratos의 융단기 구현
kratos는 github.com/bilibili/kratos/pkg/net/netutil/breaker 패키지를 통해 융단기 기능을 제공한다. 주요 인터페이스는 다음과 같다:
type Breaker interface {
Allow() error
MarkSuccess()
MarkFailed()
}
각 요청 전에 Allow()를 호출하여 요청 허용 여부를 판단하고, 결과에 따라 MarkSuccess() 또는 MarkFailed()를 호출한다.
핵심 로직 분석
kratos의 융단기 구현은 전통적인 반열림 상태를 보유하지 않으며, 대신 실패율이 지정된 기준을 초과하면 상태를 StateOpen으로 전환하고, 이후 다시 StateClosed로 복귀하도록 설계되어 있다. 핵심 알고리즘은 아래와 같다:
func (b *sreBreaker) Allow() error {
success, total := b.summary()
k := b.k * float64(success)
if total < b.request || float64(total) < k {
if atomic.LoadInt32(&b.state) == StateOpen {
atomic.CompareAndSwapInt32(&b.state, StateOpen, StateClosed)
}
return nil
}
if atomic.LoadInt32(&b.state) == StateClosed {
atomic.CompareAndSwapInt32(&b.state, StateClosed, StateOpen)
}
dr := math.Max(0, (float64(total)-k)/float64(total+1))
drop := b.trueOnProba(dr)
if drop {
return ecode.ServiceUnavailable
}
return nil
}
여기서 k 값은 성공 요청 수에 가중치를 적용한 기준이며, 요청의 실패 비율이 높아지면 dr 값이 증가하고, 최종적으로 요청을 거부하는 drop 상태로 전환된다.
사용 방법
융단기를 사용하려면 먼저 그룹을 생성하고, 각 연결이나 엔드포인트에 대해 고유한 이름으로 융단기 인스턴스를 가져온다.
brkGroup := breaker.NewGroup(&breaker.Config{})
conn.breaker = brkGroup.Get(conn.Addr)
요청 시 융단기 상태를 검사하고, 실행 결과에 따라 성공/실패를 마킹한다.
if err = conn.breaker.Allow(); err != nil {
return
}
defer func() {
if err != nil {
conn.breaker.MarkFailed()
} else {
conn.breaker.MarkSuccess()
}
}()
설정 매개변수
융단기의 동작은 다음 설정값에 의해 결정된다:
K: 실패율 기준. 기본값 1.5, 즉 성공률이 1/(1+K) 미만일 경우 트리거됨.Window: 통계 수집 시간 간격 (기본 3초).Bucket: 통계 버킷 수 (기본 10).Request: 융단기 활성화를 위한 최소 요청 수 (기본 100).
테스트 사례
실제로 300회 연속 요청을 보내고, 서버가 88번 요청 후 종료되도록 설정한 테스트에서, 융단기가 초기에는 닫힌 상태에서 성공 요청 수가 증가하면서 점차 열림 상태로 전환되는 것을 확인할 수 있었다. 이후 요청의 대부분이 거부되며, 실제 서비스가 다운된 상황에서도 클라이언트는 지속적으로 요청을 시도하지 않고, 시스템 리소스를 보호하였다.