서킷 브레이커 및 후퇴
서킷 브레이커와 레트라이
1) 왜 필요한가
네트워크는 신뢰할 수 없습니다: 대기 시간 맥동, 노드 하락, 한계에 도달합니다. 배상은 단기 고장에서 저장되며 Circuit Breaker는 계단식 고장 및 자체 DDoS로부터 시스템을 보호합니다. 정확한 타임 아웃 및 한계와의 조합은 SLO를 유지하고 테일 지연과 "nines" 가격을 안정화시킵니다.
2) 기본 원칙
첫 번째 타임 아웃, 후퇴 한 다음 Circuit Breaker.
dempotent 작업 만 재구성하십시오 (GET, dempotent 키가있는 POST/PUT 보안).
재배치 예산을 할당하십시오: 경로 당 원래 RPS의 10-15%.
고장을 현지화하십시오: 벌크 헤드 (별도의 풀/쿼터) + 속도 제한.
분해 중-빠른 고장 (실패), 우아한 분해/스터브.
3) 리트레이 시맨틱
퇴각시기
일시적 오류: 타임 아웃, 5xx, 네트워크 사용 불가, 429 ('재시도 후' 이후).
철회 할 수 없습니다: 명백한 비즈니스 오류 (4xx § 429), dempotence가없는 부작용 (키없는 지불).
전략
지수 백오프 + 지터 (전체 또는 짝수): 재추적 무리를 부드럽게합니다.
최대 시도: 1-2 (드물게 3) -일반적으로 더 많은 것이 유해합니다.
예산: 서비스 당 글로벌 재 트레이 카운터/초 및 요청 당 "재 시도 토큰".
Hedging (희귀): t- 양자 (p95) 이후의 요청의 병렬 이중 - 엄격하게 dempotent 판독에 대해서만.
python base = 100 # ms for attempt in range(1, max_attempts+1):
try:
return call()
except Transient as e:
if attempt == max_attempts: raise sleep_ms = min(cap_ms, base 2(attempt-1))
sleep(random(0, sleep_ms)) # full jitter
4) 시간 초과 및 "빠른 실패"
클라이언트 타임 아웃 <업스트림 타임 아웃: "좀비" 요청을 누적하지 않도록.
자세한 정보: 시간 초과 연결, 읽기 시간 초과, 전체 마감일.
꼬리 인식 타임 아웃: p95/p99 + 작은 마진을 목표로합니다.
공통 마감일 필드 (예: gRPC '마감일') 를 사용하여 체인 아래로 캐스팅하십시오.
5) 회로 차단기: 작동 방식
상태:- 휴무: 트래픽을 통과하고 오류/대기 시간을 계산합니다.
- 열기: 즉시 거절합니다 (또는 예비 답변).
- Half-Open: 테스트 쿼리; 성공하면 닫힙니다.
- 오류/타임 아웃은 창 N 요청/초 당 X% 를 초과하거나 임계 값 이상으로 p99를 초과합니다.
- 롤링 통계 및 최소 볼륨은 관련이 있습니다 (예: 이하 50 개의 쿼리).
6) 벌크 헤드, 할당량 및 분할 및 정복
업스트림 및 기능 별 연결의 별도 풀.
기내 요청에 대한 쿼터; 불필요한-빠른 거부.
부족한 경우-피처 플래그의 저하.
7) 주변 미터 통합 (Envoy/Istio/Nginx)
특사 (재 시도 + 이상 + CB, 아이디어):yaml routes:
- match: { prefix: "/api" }
route:
cluster: upstream_api timeout: 2s retry_policy:
retry_on: "connect-failure,reset,retriable-4xx,5xx"
num_retries: 2 per_try_timeout: 600ms retry_back_off: { base_interval: 100ms, max_interval: 800ms }
hedge_policy:
hedge_on_per_try_timeout: true initial_requests: 1 additional_request_chance: { numerator: 5, denominator: HUNDRED } # 5%
clusters:
- name: upstream_api circuit_breakers:
thresholds:
- priority: DEFAULT max_connections: 500 max_requests: 1000 max_retries: 200 outlier_detection:
consecutive_5xx: 5 interval: 5s base_ejection_time: 30s max_ejection_percent: 50
Istio (VirtualService 결함/재 시도, 압축 예):
yaml apiVersion: networking. istio. io/v1beta1 kind: VirtualService spec:
hosts: ["payments"]
http:
- route: [{ destination: { host: payments } }]
timeout: 2s retries:
attempts: 2 perTryTimeout: 600ms retryOn: "5xx,connect-failure,refused-stream,reset"
Nginx Ingress (주석):
yaml nginx. ingress. kubernetes. io/proxy-connect-timeout: "2"
nginx. ingress. kubernetes. io/proxy-read-timeout: "2"
nginx. ingress. kubernetes. io/proxy-next-upstream: "error timeout http_502 http_503 http_504"
nginx. ingress. kubernetes. io/proxy-next-upstream-tries: "2"
8) 라이브러리 및 코드 (스택 스 니펫)
자바 (Resilience4j):java var cb = CircuitBreaker. ofDefaults("psp");
var retry = Retry. of("psp-retry",
RetryConfig. custom()
.maxAttempts(2)
.waitDuration(Duration. ofMillis(200))
.intervalFunction(IntervalFunction. ofExponentialRandomBackoff(100, 2. 0, 0. 5) )//jitter
.retryExceptions(SocketTimeoutException. class, IOException. class)
.build());
Supplier<Response> decorated =
CircuitBreaker. decorateSupplier(cb,
Retry. decorateSupplier(retry, () -> client. call()));
return Try. ofSupplier(decorated)
.recover(BusinessException. class, fallback())
.get();
이동 (컨텍스트 마감일 + 백오프):
go ctx, cancel:= context. WithTimeout(context. Background(), 2time. Second)
defer cancel()
var lastErr error for i:= 0; i < 2; i++ {
reqCtx, stop:= context. WithTimeout(ctx, 600time. Millisecond)
lastErr = call(reqCtx)
stop()
if lastErr == nil { break }
sleep:= time. Duration(rand. Intn(1<<uint(7+i))) time. Millisecond // full jitter time. Sleep(min(sleep, 800time. Millisecond))
}
if lastErr!= nil { return fastFail() }
노드. js (+ p- 재 시도):
js import pRetry from 'p-retry';
await pRetry(() => got(url, { timeout: { connect: 500, request: 2000 } }), {
retries: 2,
factor: 2,
randomize: true,
minTimeout: 100,
maxTimeout: 800,
onFailedAttempt: e => { if (isBusiness(e)) throw e; }
});
9) 재 트레이 및 SLO 예산
타입 재 시도 토큰: 각 리트레이는 토큰을 사용합니다. 수영장은 제한되어 있습니다
오류 예산과 관련하여: 연소율이 임계 값을 초과하면 배상을 끄고 CB를 더 자주 열고 열화를 켜십시오.
카나리아 릴리스: 카나리아에서는 시도와 토큰을 줄이십시오.
10) 헤징 (주의)
p95 마감일 이후에 추가 요청을 실행하여 패자를 취소하십시오.
읽기 및 "안전한" demempotent 작업에만 해당됩니다. 점유율을 제한합니다 (1-5%).
업스트림의 부하 증가를 조심하십시오.
11) 관찰 가능
경로에 따른 RED 지표: 속도, 오류, 지속 시간 (p50/p95/p99).
CB 메트릭: 상태 (개방/반 개방), 개방 속도, 누락/거부 요청.
배상: 시도/요청, 재 시도 속도, 구운 토큰.
주변 측정기: 특이 치 배출, 배출 속도.
추적: 주석으로 '시도', 'cb _ state', '헤지 = 참', 캐스트 'trace _ id'.
12) 아키텍처 통합
각 중요한 업스트림에 대한 벌크 헤드 + CB.
대기열/asynchron: 미친 타임 아웃 대신 긴 작업.
캐시/스터브: 실패시 중요하지 않은 기능을 사용합니다.
Autoscale: 나쁜 휴양지를 보충하지 않습니다. 폭풍을 먼저 멈추십시오.
13) 반 패턴
타임 아웃이없는 배상 → 얼어 붙은 연결 및 수영장 고갈.
비정규 트랜잭션 (이중 상각) 을 반복하십시오.
한도와 지터가없는 무한 지수 성장.
모든 업스트림 → 드래그 앤 드롭 오류에 대한 단일 CB
429 무시/' 재생 후 '.
클라이언트 타임 아웃은 업스트림보다 길다 (또는 전혀 아님).
레트라로 인한 "치료" 비즈니스 오류.
14) 구현 점검표 (0-30 일)
0-7 일
경로와 그 등급을 식별하십시오.
타임 아웃 (연결/읽기/전체) 을 설정하고 최소 배상 (× 1) 및 기본 CB를 활성화하십시오.
메인 업스트림의 풀/할당량 (벌크 헤드) 을 분리하십시오.
8-20 일
지터 및 글로벌 리트레이 예산, 재 시도 속도 경고 포함.
로우 프리오 기능에 대한 빠른 오류, 주변의 이상 배출 설정.
RED + CB/재시도 대시 보드, 태그 트레일.
21-30 일
카나리아 리트레이 프로필 (시도 횟수 감소), 게임 데이 "업스트림 슬로우/플랩".
문서 정책: 누가 복귀, 제한, 예외.
눈이 아닌 데이터에 따라 p95/p99 및 타임 아웃을 검토하십시오.
15) 성숙도 지표
노선의 100% 에는 타임 아웃이 있으며 문서화 된 retray/NE 정책이 있습니다
재시동 속도는 예산에 맞으며 (10-15%) 사고에는 급증이 없습니다.
수영장 전체가 떨어지기 전에 CB가 발사됩니다. 계단식 실패가 없습니다.
트레일은 시도/헤징을 보여줍니다. p99는 피크에서 안정적입니다.
카나리아 릴리스는 "주의" 리트레이 프로파일을 사용합니다
16) 짧은 구성 예
Resilience4j YAML (스프링 부츠, и
yaml resilience4j:
circuitbreaker:
instances:
psp:
slidingWindowType: COUNT_BASED slidingWindowSize: 100 minimumNumberOfCalls: 50 failureRateThreshold: 50 waitDurationInOpenState: 30s permittedNumberOfCallsInHalfOpenState: 5 retry:
instances:
psp:
maxAttempts: 2 waitDuration: 200ms enableExponentialBackoff: true exponentialBackoffMultiplier: 2. 0 retryExceptions:
- java. net. SocketTimeoutException
- java. io. IOException
특허 속도 제한 (아이디어 조각):
yaml rate_limits:
- actions:
- generic_key: { descriptor_value: "api. payments" }
17) 결론
지속 가능성은 규율입니다. 타임 아웃 → 후퇴 (지터 및 예산 포함) → 회로 차단기 + 격벽/할당량 및 빠른 거부. 경계 (이상 배출) 를 설정하고 RED/CB/재시도 대시 보드를 끊고 demempotency 정책을 수정하며 비즈니스 SLI를 잊지 마십시오. 그런 다음 간단한 실패는 보이지 않으며 실제 사건은 계단식 낙하로 바뀌지 않습니다.