Circuit Breaker et Retrai
Circuit Breaker et Retrai
1) Pourquoi est-ce nécessaire
Les réseaux ne sont pas fiables : la latence pulse, les nœuds tombent, les limites sont atteintes. Les retraits sont sauvés des pannes à court terme, et le Circuit Breaker protège le système contre les pannes en cascade et le « samo-DDoS ». La combinaison avec les délais corrects et les limites maintient le SLO, stabilise les retards de queue et le prix du « neuf ».
2) Principes de base
D'abord les timouts, puis les retraits, puis le Circuit Breaker.
Retraim uniquement les opérations idempotent (GET, POST/PUT sécurisé avec clé idempotent).
Allouer un budget rétroactif : ≤ 10 à 15 % du RPS initial par itinéraire.
Localiser l'échec : bulkhead (pools séparés/quotas) + rate-limit.
Lors de la dégradation - panne rapide (fail-fast), graceful-degradation/bouchons.
3) La sémantique des rétrogrades
Quand se rétracter
Erreurs de transit : timeouts, 5xx, indisponibilité réseau, 429 (après « Retry-After »).
Vous ne pouvez pas rétracter : erreurs commerciales évidentes (4xx ≠ 429), side-effects sans idempotence (paiement sans clé).
Stratégies
Backoff exponentiel + jitter (complet ou uniforme) : lisse les meutes de retraits.
Max attempts : 1-2 (rarement 3) - plus généralement nocif.
Budget : compteur global de retraits/s par service et per-request « retry tokens ».
Hedging (rarement) : prise parallèle de la requête après t-quantii (p95) - uniquement pour les lectures strictement idempotentes.
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) Taimauts et « panne rapide »
Client timeout <upstream timeout : afin de ne pas économiser les requêtes « zombies ».
Делите: connect timeout, read timeout, overall deadline.
Tail-aware Timauts : visons p95/p99 + petit stock.
Utilisez le champ de deadline partagé (par exemple, gRPC 'deadline') et faites-le glisser vers le bas de la chaîne.
5) Circuit Breaker : Comment fonctionne
États :- Closed : manque le trafic, compte les erreurs/latence.
- Open : donne immédiatement une réponse rapide (ou une réponse de secours).
- Half-Open : demandes de vérification ; s'il réussit, il ferme.
- Les erreurs/temporisations dépassent la proportion de X % par fenêtre de N requêtes/secondes ou p99 au-dessus du seuil.
- Statistiques glissantes pertinentes et volume minimum (par exemple, 50 demandes ≥).
6) Bulkhead, quotas et « divise et domine »
Pools séparés des composés per-upstream et per-fich.
Quotas de demandes in-flight ; l'excès est un refus rapide.
En cas de pénurie, la dégradation des fiches à faible priorité (feature flags).
7) Intégration avec le périmètre (Envoy/Istio/Nginx)
Envoy (retry + outlier + CB, idée) :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 (Fault/Retry VirtualService, exemple compressé) :
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 (annotations) :
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) Bibliothèques et code (piles de clips)
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();
Go (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() }
Node. 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) Budget rétroactif et SLO
Entrez retry tokens : chaque rétro dépense un token ; le pool est limité.
Liez à error-budget : lorsque le taux est au-dessus du seuil - désactiver les retraits, ouvrir CB plus souvent, activer la dégradation.
Sorties canaries : sur les canaries, réduire les tentatives et les jetons.
10) Hedging (prudent)
Lancez une requête supplémentaire après la date limite p95 en annulant le perdant.
Uniquement pour les lectures et les opérations idempotentes « sûres » ; limitez la part (≤ 1-5 %).
Surveillez l'augmentation de la charge de travail sur l'apstream.
11) Observabilité
Métriques RED par itinéraire : Taux, Erreur, Durée (p50/p95/p99).
CB-métriques : état (open/half-open), taux d'ouverture, requêtes manquées/refusées.
Retrai : attempts/request, retry-rate, tokens brûlés.
Périmètre : outlier-ejection, ejection-rate.
Traces : annotez 'retry _ attempt', 'cb _ state', 'hedged = true', vérifiez 'trace _ id'.
12) Intégration avec l'architecture
Bulkhead + CB pour chaque appel critique.
Files d'attente/asynchrones : pour les longues opérations au lieu des temps fous.
Cache/bouchons : pour les fiches non critiques dans fail-open.
Auto Skale : ne compense pas les mauvaises retraits - d'abord arrêter la « tempête ».
13) Anti-modèles
Les retraits sans temporuts → les connexions « dépendantes » et l'épuisement des pools.
Répétition d'opérations non-ponctuelles (doubles débits).
Croissance exponentielle infinie sans cap et sans jitter.
Un CB unique sur tous les apstrymes → le glisser-déposer sur l'ensemble du produit.
Ignorer 429/' Retry-After '.
Le délai du client est plus long que l'apstream (ou pas du tout).
« Traiter » les erreurs commerciales avec des retraits.
14) Chèque de mise en œuvre (0-30 jours)
0-7 jours
Identifiez les itinéraires et leur idempotence.
Définissez les délais (connect/read/overall), activez les retraits minimaux (× 1) et CB par défaut.
Diviser les pools/quotas (bulkhead) pour les aptrimes de base.
8-20 jours
Incluez le jitter et le budget global des retraits, les alertes retry-rate.
Réglez outlier-ejection sur le périmètre, une défaillance rapide pour low-prio fich.
Dashboards RED + CB/Retry, remorques marquées.
21-30 jours
Profils canaris de rétrograves (moins de tentatives), game-day « apstream lent/flash ».
Documenter la politique : qui/quoi est rétrograde, limites, exceptions.
Révisez p95/p99 et les délais selon les données, pas pour les yeux.
15) Métriques de maturité
100 % des itinéraires ont des délais et une politique documentée de retraits/ST.
Retry-rate est mis dans le budget (≤ 10-15 %), il n'y a pas de surtension en cas d'incident.
Les CB sont déclenchés avant que tout le pool ne tombe ; pas de défaillances en cascade.
Les tracés montrent des tentatives/hedging ; p99 est stable aux pics.
Les versions canaries utilisent un profil « prudent » des retraits.
16) Courts exemples de configurations
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
Envoy rate-limit (fragment d'idée) :
yaml rate_limits:
- actions:
- generic_key: { descriptor_value: "api. payments" }
17) Conclusion
La stabilité est une discipline : таймауты → ретраи (avec джиттером et le budget) → Circuit Breaker + les bulkhead/quotas et le refus rapide. Ajustez le périmètre (outlier-ejection), accrochez les dashboards RED/CB/Retry, fixez la politique d'idempotence et n'oubliez pas les SLI d'affaires. Les brèves défaillances resteront alors invisibles et les vrais incidents ne se transformeront pas en chutes en cascade.