Întrerupător de circuite și retrageri
Întrerupător și Retrai
1) De ce aveți nevoie de ea
Rețelele nu sunt fiabile: latența pulsează, nodurile cad, limitele sunt atinse. Retraiele sunt salvate de la defecțiuni pe termen scurt, iar Circuit Breaker protejează sistemul de defecțiuni în cascadă și auto-DDoS. Combinația cu termenele și limitele corecte păstrează SLO, stabilizează întârzierile cozii și prețul „noilor”.
2) Principii de bază
În primul rând timeout, apoi se retrage, apoi Circuit Breaker.
Retraim numai operațiunile idempotente (GET, Secure POST/PUT cu cheie idempotentă).
Alocați un buget de retractare: ≤ 10-15% din SPR-ul original pe traseu.
Localizați eșecul: perete etanș (piscine separate/cote) + rată-limită.
În timpul degradării - eșec rapid (eșec-rapid), degradare grațioasă/ciot.
3) Retrage semantica
Când să se retragă
Erori tranzitorii: timeout, 5xx, indisponibilitate reţea, 429 (după 'Retry-After').
Nu puteți retrage: erori evidente de afaceri (4xx ≠ 429), efecte secundare fără idempotență (plată fără cheie).
Strategii
Backoff exponențial + jitter (complet sau chiar): netezește roiurile de retrasări.
Încercări max: 1-2 (rareori 3) - mai mult este de obicei dăunător.
Buget: counter retray global/sec per service și per-request „încercați din nou jetoanele”.
Gard viu (rar): dublu paralel al cererii după t-quantile (p95) - numai pentru citiri strict idempotente.
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) Timeout și „eșec rapid”
Timeout client <amonte timeout: astfel încât să nu se acumuleze „zombie” cereri.
Делите: conectați timeout, citiți timeout, termenul general.
Timpi conștienți de coadă: țintiți pentru p95/p99 + marjă mică.
Utilizați un câmp limită comun (de exemplu, gRPC „termen limită”) și aruncați-l în jos lanțul.
5) Întrerupător de circuit: Cum funcționează
State:- Închis: trece traficul, numără erorile/latența.
- Deschis: dă imediat un refuz rapid (sau răspuns de rezervă).
- Semi-deschis: interogări de testare; dacă are succes, se închide.
- Erorile/timeout-urile depășesc X% per fereastră N cereri/secunde sau p99 peste prag.
- Statisticile de rulare și volumul minim sunt relevante (de exemplu, ≥ 50 de întrebări).
6) Perete etanș, cote și divide și cuceri
Piscine separate de conexiuni per-amonte și per-feature.
Contingente pentru solicitările în timpul zborului; de prisos - refuz rapid.
În caz de lipsă - degradarea steagurilor caracteristice.
7) Integrarea perimetrului (Trimisul/Istio/Nginx)
Trimisul (încercați din nou + outlier + CB, idee):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 (defect VirtualService/retry, exemplu comprimat):
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 (adnotări):
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) Biblioteci și cod (fragmente de stivă)
Java (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();
Du-te (context deadline + backoff):
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() }
Nod. js (got + p-retry):
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) Retray și bugetul SLO
Tip jetoane Retry: Fiecare retray petrece un jeton; piscina este limitată.
Asociați-vă cu bugetul de eroare: dacă rata de ardere este peste prag, opriți retraiele, deschideți CB mai des, porniți degradarea.
Canare de presă: Pe canari, reduce încercările și token-uri.
10) Gard viu (prudență)
Executați o cerere suplimentară după termenul limită p95, anularea pierzătorului.
Numai pentru citiri și operațiuni idempotente „sigure”; limita cota (≤ 1-5%).
Uita-te pentru o creștere a sarcinii pe amonte.
11) Observabilitate
Valorile RED de-a lungul traseelor: Rata, Eroare, Durata (p50/p95/p99).
Valori CB: stare (deschis/semi-deschis), rata de deschidere, cererile ratate/refuzate.
Retrogradări: încercări/cerere, reîncercare, jetoane arse.
Perimetru: exterior-ejectare, rata de ejectare.
Urme: adnotați 'retry _ try', 'cb _ state', 'hedged = true', cast 'trace _ id'.
12) Integrarea arhitecturii
Perete etanș + CB pentru fiecare critică în amonte.
Cozi/asincron: pentru operații lungi în loc de timeout nebun.
Cache/stubs: pentru caracteristici non-critice atunci când nu reușesc să deschidă.
Autoscale: Nu compensează retragerile rele - opriți mai întâi furtuna.
13) Anti-modele
Retrocedări fără întreruperi de timp → conexiuni înghețate și epuizarea piscinelor.
Repetați tranzacțiile non-idempotente (dublu write-off).
Creștere exponențială infinită fără capac și jitter.
Un singur CB la toate în amonte → drag and drop eșec la întregul produs.
Ignorarea 429/„ Retry-After ”.
Timeout-ul clientului este mai lung decât cel din amonte (sau deloc).
„Trata” erori de afaceri cu retras.
14) Lista de verificare a implementării (0-30 zile)
0-7 zile
Identificați rutele și idempotența lor.
Setați termenele (conectați/citiți/în ansamblu), activați retraiele minime (× 1) și CB implicit.
Separați piscinele/cotele (perete etanș) pentru principalul amonte.
8-20 zile
Includeți bugetul de redistribuire și bugetul global, alertele de reîncercare.
Configurați ejecția exterioară pe perimetru, eșec rapid pentru caracteristica low-prio.
RED + CB/Retry tablouri de bord, etichetate trasee.
21-30 zile
Canare profile retray (mai puține încercări), joc-zi "în amonte lent/flaps'.
Politica documentului: cine/ce retrasează, limite, excepții.
Revizuirea p95/p99 și timeout în funcție de date, nu de ochi.
15) Valorile maturității
100% din rute au timeout-uri și politici de retray/NE documentate.
Rata de reîncercare se încadrează în buget (≤ 10-15%), nu există vârfuri în incidente.
CB-urile trag înainte de căderea întregului bazin; fără eşecuri în cascadă.
Trasee arată încercări/acoperire; p99 este stabil în vârfuri.
Eliberările Canare folosesc un profil „atent”.
16) Exemple de configurare scurtă
Resilience4j YAML (Spring Boot, идея):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
Limita ratei trimisului (fragment de idee):
yaml rate_limits:
- actions:
- generic_key: { descriptor_value: "api. payments" }
17) Concluzie
Sustenabilitatea este o disciplină: timeout-uri → retrageri (cu jitter și buget) → Circuit Breaker + pereți etanși/cote și respingere rapidă. Configurați un perimetru (outlier-ejection), închideți tablourile de bord RED/CB/Retry, fixați politica de idempotență și nu uitați de SLI de afaceri. Apoi, eșecurile scurte vor rămâne invizibile, iar incidentele reale nu se vor transforma în căderi în cascadă.