Circuit Breaker

회로 차단기 회로 차단기는 전기 회로에서 과부하가 걸리거나 단락으로 인한 피해를 막기 위해 자동으로 회로를 정지시키는 장치이다. 과부하 차단기와 누전 차단기로 나뉜다. 퓨즈와 다른 점은, 차단기는 어느 정도 시간이 지난 뒤에는, 원래의 기능이 동작하도록 복귀된다.

회로 차단.. 아니 코드 차단기

Circuit Breaker는 거의 처음접하는분이 많을겁니다.
Circuit Breaker의 주목적은 기능 모듈에서 장애가 나면 다른 모듈로 장애가 전파되는것을 막기위해서 사용합니다.
보통의 Circuit Breaker는 MSA 아키텍쳐에서 사용되는것으로 알고 있습니다.
셀메이트 모바일 포스에서는 외부 통신 API를 제어하기위해서 채택하여 적용해보았습니다.

Circuit Breaker의 특징

  • Circuit Breaker는 성공임계값, 실패임계값, 실패타임아웃을 가지고 제어.
  • 통신이 가능한 close상태, 통신이 가능한지 확인 및 테스트를 해보는 half-open 상태, 통신이 불가능한 open 상태
  • 하나의 모듈의 실패가 다른 모듈로의 실패를 전파하지 않음
  • 실패 임계값을 넘어서면 타임아웃 시간동안 요청을 전송하지않고 Throw를 발생시킴 (fail fast)

일단 동작 구현!

서킷 브레이커 특징

  1. open 상태에서는 실패 timeout 임계치까지 새로운 요청을 보내서는 안됨.
  2. close 상태에서는 정상적인 요청을 보내며 fail임계치 값을 초기화 시킴.
  3. halfOpen 상태에서의 요청은 정상적으로 처리하되 성공 임계치 까지 상태를 유지하며 요청을 보낸다 이때, 다시 실패가 됬을경우 open 상태로 돌아간다.
  • 실패 처리
1
2
3
4
5
6
7
8
  void _fail() {
  _failCount++;

  if (_failCount >= failureThreshold) {
    _state = State.open;
    _nextAttempt = DateTime.now().add(timeout);
  }
} 
  • 성공처리
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  void _success() {
    _failCount = 0;

    if (_state == State.halfOpen) {
      _successCount++;

      if (_successCount > successThreshold) {
        _successCount = 0;
        _state = State.closed;
      }
    }
  }
  • 상태값 관리
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  Future<T?> execute(R reqData) async {
    if (_state == State.open) {
      if (_nextAttempt.millisecondsSinceEpoch <=
          DateTime.now().millisecondsSinceEpoch) {
        _state = State.halfOpen;
      } else {
        throw Exception('Circuit suspended');
      }
    }
    T? result;

    if (req == null) {
      throw Exception("Not Setting req");
    }

    try {
      result = await req!(reqData);
      _success();
    } catch (_) {
      _fail();
    }

    return result;
  }

셀메이트 포스에서는?

Circuit Breaker 구현 원인

판매중 셀메이트 서버가 죽거나 오류로인해 판매하던 결제가 실패하면 안되기 때문에, 결제는지속하되, 판매데이터를 모아서 정상화가 되었을때 뿌려주는 목표가 있었습니다.

그냥 모아 뒀다가 뿌리면?

  • 상황 가정
    1개의 매장에서 서버가 죽은시간동안 10개의 주문을 생성을해서 전송 대기라고 가정. 100개의 매장에서 모두 대기가 발생했을때 1000개의 요청이 생성된 상태.
  • case1
    서버가 복구되는 시점으로 보고 있다가 한꺼번에 던지면 재미있는 일이 나는 상황.
  • case2
    타임아웃처리가 나는데 타임아웃 시간동안 대기를 하는 현상.
  • case3
    서버는 살아 있으나 서버 응답이 오류가 계속 나는데 계속 서버를 호출하고 있다!

그래서? 위에 상황 가정에 대한 구현은?

  • 3번(실패 임계치) 의 실패가 일어나면 Circuit Breaker의 상태는 open상태로 바꾸어 요청은 설정한 timeout시간만큼 서버요청을 무시해버린다. 하지만 결제는 정상적으로 된것처럼 보이도록!

  • timeout이 완료가 되면 Circuit Breaker는 half-open 상태가 되어 요청을 보내본다! 이때 결제도 가능하다! 하지만 이때 API요청 판매데이터저장이 정상적으로 성공 임계치까지 도달하면 Circuit Breaker는 close 상태가된다.

  • 이때 close상태가 되면 이전에 half-open, open 상태에서의 fail처리건을 처리한다. 하지만 half-open 상태에서의 실패는 바로 open상태로 돌아가 다시 timeout이 걸려 요청이 다시 무시가된다.

결론적으론,

  • 위의 구현은 무조건 결제가 일어나며 판매를 지속 할 수 있다,
  • 오류 임계치에서는 서버에서의 호출 자체를 무시하여 timeout의 시간을 줄여 빠르게 판매가 가능하며, 서버 복구까지의 오류호출에 대한 불필요 호출을 줄일 수 있다.
  • 때론 실패건들의 한꺼번 요청에 대해서 시간차이가 발생하여 서버에서의 요청처리 부하가 적어질것으로 기대된다.