别让一个慢目标拖垮枢纽
한국어 원문으로 표시합니다.
목표
서킷 브레이커와 대상별 벌크헤드로 느리거나 죽은 대상 하나가 허브 전체를 끌어내리지 못하게 한다.
왜 중요한가
타임아웃은 한 건을 얼마나 기다릴지만 정한다. 죽은 대상을 계속 부르면 기다리는 스레드가 쌓여 허브가 먼저 숨이 막히고, 상관없는 거래까지 함께 멈춘다. 빨리 거절하는 장치가 대상마다 따로 있어야 장애가 번지지 않는다.
단계
/root/eaimw/breaker/breaker.py에CircuitBreaker(failure_threshold, reset_timeout, clock=time.monotonic)를 만든다.state(CLOSED·OPEN·HALF_OPEN),allow(),record_success(),record_failure(). 연속 실패가 기준에 닿으면 OPEN, OPEN 이면allow()는 False. 시각은 반드시clock()으로 읽는다(채점기가 가짜 시계를 넣는다).- OPEN 에서
reset_timeout이 지나면allow()가 한 번만 True 를 주고 HALF_OPEN 이 된다. 시험 호출이 성공하면 CLOSED, 실패하면 다시 OPEN(시간을 처음부터). 여러 스레드가 동시에 물어도 한 건만 허용한다(잠금). cp /opt/lab/fixtures/eaimw/breaker/relay_base.py /root/eaimw/breaker/relay.py로 시작해--fail-threshold(기본 5)·--reset-timeout(기본 10) 인자를 더하고, 대상 호출을 브레이커로 감싼다. 열려 있으면 부르지 않고 즉시 E904. 이 단계에서는 0000 이 아닌 결과를 실패로 센다.--max-inflight(기본 10) 동시 처리 한도를 둔다. 한도가 차 있으면 기다리지 않고 즉시 E905.- 브레이커와 동시 처리 한도를 대상(CORE·CLAIM)마다 따로 둔다. 계정계의 고장이나 포화가 청구 거래에 번지지 않아야 한다.
--breaker-log(기본/root/eaimw/breaker/breaker.log)에 상태가 바뀔 때마다<시각>|<대상>|<FROM>-><TO>한 줄을 남긴다.- 실패로 세는 것을 시스템 오류(E500·E901·E902)로 좁힌다. 업무 거절(B2xx)은 대상이 건강하게 답한 것이므로 성공으로 센다.
참고
- 동시 처리 한도:
threading.BoundedSemaphore(n)의acquire(blocking=False)가 False 면 한도가 찬 것. 끝나면 반드시release()(try/finally). - 상태 전이 기록:
allow()앞뒤와record_*()앞뒤의state를 비교하면 바뀐 순간을 알 수 있다. - 계정계 픽스처 스위치:
curl -XPOST localhost:9201/_ctl -d '{"mode":"fail"}'(500),"slow"(지연),"normal". 통계/_stats의calls·max_inflight. - 흔한 실수: 허브 전체에 브레이커 하나, 한도를 넘은 요청을 줄 세워 기다리게 하기, HALF_OPEN 에서 여러 건 허용,
time.time()을 직접 불러 가짜 시계를 무시하기.
연속 실패가 쌓이면 회로를 연다
/root/eaimw/breaker/breaker.py 의 CircuitBreaker 가 연속 실패 기준에서 OPEN 이 되고, OPEN 이면 allow() 가 False 다.
성공하면 실패 수를 0 으로 되돌려 '연속' 을 셉니다. 열린 시각은 time.time() 이 아니라 self.clock() 으로 기록하세요 — 채점기가 시계를 쥡니다.
한 건으로 떠보고 닫는다
reset_timeout 뒤 HALF_OPEN 에서 시험 호출 한 건만 허용하고, 성공이면 CLOSED, 실패면 다시 OPEN 으로.
allow() 안에서 OPEN 이고 시간이 지났으면 HALF_OPEN 으로 바꾸고, 시험 호출이 이미 나갔는지 표시를 둡니다. 두 스레드가 동시에 물어도 한쪽만 True 를 받게 잠금 안에서 판단하세요.
열려 있으면 부르지 않는다
relay_base.py 를 복사해 대상 호출을 브레이커로 감싸고, 열려 있으면 E904 로 즉시 답한다(--fail-threshold·--reset-timeout).
handle_tx 가 감쌀 자리입니다. allow() 가 False 면 call_target 을 부르지 않고 돌아오고, 부른 뒤에는 결과에 따라 record_success/record_failure 를 부릅니다.
한도를 넘으면 기다리지 않는다
--max-inflight 동시 처리 한도를 두고, 차 있으면 즉시 E905 로 답한다.
BoundedSemaphore 의 acquire(blocking=False) 가 False 면 한도가 찬 것입니다. 부른 뒤에는 예외가 나도 release 되게 try/finally 로.
대상마다 격벽을 나눈다
브레이커와 동시 처리 한도를 CORE·CLAIM 대상마다 따로 두어, 계정계의 고장·포화가 청구로 번지지 않게 한다.
하나씩 두던 것을 대상 이름을 열쇠로 한 딕셔너리로 바꿉니다. 격벽이 하나면 한 구획의 물이 배 전체로 찹니다.
상태 전이를 기록한다
--breaker-log 에 상태가 바뀔 때마다 '시각|대상|FROM->TO' 한 줄을 남긴다.
allow() 전후, record 전후의 state 를 비교해 다르면 한 줄을 씁니다. 여러 스레드가 쓰므로 잠금을 잡고 씁니다.
업무 거절은 실패가 아니다
실패로 세는 것을 E500·E901·E902 로 좁히고, B2xx 는 성공으로 센다.
잔액 부족은 계정계가 건강하게 답한 것입니다. 이것을 실패로 세면 월말의 업무 거절만으로 멀쩡한 계정계로 가는 길을 끊습니다.