מפסק מעגלים ונסיגה
מפסק מעגל ורטראי
1) למה אתה צריך את זה
רשתות אינן אמינות: פעימות לחות, צמתים נופלים, גבולות מגיעים. המגשים נשמרים מכישלונות לטווח קצר, ו-Circuit Breaker מגן על המערכת מפני כשלים מפלים ו-DDOS עצמי. השילוב עם פסקי הזמן והמגבלות הנכונים משמר את SLO, מייצב עיכובים בזנב ואת המחיר של ”תשיעיות”.
2) עקרונות בסיסיים
פסקי זמן ראשונים, ואז נסיגה, ואז מפסק מעגל.
רטריים רק פעולות אידמפוטנטיות (GET, secure POST/PUT עם מפתח אידמפוטנטי).
להקצות תקציב מגש מחדש: סמן 10-15% של RPS המקורי לכל מסלול.
כשל מקומי: מחיצה (בריכות נפרדות/מכסות) + מגבלת קצב.
במהלך ההשפלה - כשל מהיר (אל-כשל), הידלדלות חיננית/יתד.
3) מגש סמנטיקה
מתי לסגת?
שגיאות טרנזיטיביות: פסקי זמן, 5xx, רשת לא זמינה, 429 (אחרי ”Retry-After”).
אתה לא יכול לחזור בך: שגיאות עסקיות ברורות (4xx = 429), תופעות לוואי ללא אידמפוטנציה (תשלום ללא מפתח).
אסטרטגיות
גיבוי מעריכי + jitter (מלא או זוגי): Smots out swarms of recears.
ניסיונות מקסימליים: 1-2 (לעתים נדירות 3) - יותר הוא בדרך כלל מזיק.
תקציב: מגש מחדש גלובלי נגד/שניות לכל שירות ו ”אסימונים” לפי בקשה.
גידור (נדיר): כפולה מקבילה של הבקשה לאחר t-קוונטל (p95) - רק לקריאות אידמפוטנטיות בלבד.
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 'ladinal') והטיל אותו במורד השרשרת.
5) מפסק נפתי: כיצד הוא פועל
מדינות:- סגור: עובר תנועה, סופר שגיאות/איחור.
- פתח: מייד נותן סירוב מהיר (או תשובה חלופית).
- חצי פתוח: שאילתות מבחן; אם מצליח, הוא נסגר.
- שגיאות/פסקי זמן עולים על X% לכל חלון בקשות N/שניות או p99 מעל הסף.
- סטטיסטיקות גלגול ונפח מינימלי רלוונטיים (לדוגמה, שאילתות של 50 שאילתות).
6) מחיצות, מכסות והפרד ומשול
בריכות נפרדות של לכל זרם וחיבורים לכל תכונה.
מכסות לבקשות בטיסה; סירוב מיותר - מהיר.
במקרה של מחסור - השפלה של דגלים מאפיינים.
7) אינטגרציה היקפית (שליח/איסטיו/נגינקס)
שליח (מחדש + outlier + 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
איסטיו (Virtual Service fault/retry, דוגמה דחוסה):
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) ספריות וקוד (חטיפי מחסנית)
Java (Resalience4j):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 (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) מגש ותקציב SLO
Type retry אסימונים: כל מגש מוציא אסימון; הבריכה מוגבלת.
מקושרים לתקציב שגיאות: אם שיעור השרפה הוא מעל הסף, לכבות מגשים מחדש, לפתוח CB לעיתים קרובות יותר, להפעיל השפלה.
קנרית משחררת: על קנריות, להפחית ניסיונות ואסימונים.
10) גידור (זהירות)
הפעל בקשה נוספת לאחר המועד הסופי p95, ביטול המפסיד.
רק לקריאה ו ”בטוח” פעולות אידמפוטנטים; הגבל את הנתח (1-5%).
שים לב לעלייה בעומס במעלה הזרם.
11) יכולת תצפית
מדדים אדומים לאורך המסלולים: קצב, שגיאה, משך (p50/p95/p99).
CB metrics: מצב (פתוח/פתוח למחצה), קצב פתיחה, פספוס/סירוב בקשות.
מגשים מחדש: נסיונות/בקשה, קצב מחדש, אסימונים שרופים.
היקפי: החוצה-פליטה, קצב פליטה.
עקבות: annotate 'retry _ tresure', 'cb _ state', 'hedged = true', cast 'trace _ id'.
12) שילוב ארכיטקטורה
מחיצה + CB עבור כל קריטי במעלה הזרם.
תורים/אסינכרון: למבצעים ארוכים במקום פסקי זמן מטורפים.
מטמון/גפנים: עבור מאפיינים שאינם קריטיים בעת אל-כשל.
לא מפצה על נסיגות רעות - לעצור את הסערה ראשונה.
13) אנטי דפוסים
מגשים מחדש ללא פסקי זמן * חיבורים קפואים ודלדול בריכות.
חזור על עסקאות לא אידמפוטנטיות (כתיבה כפולה).
צמיחה אקספוננציאלית אינסופית ללא כובע וריגוש.
CB יחיד לכל הגרירה במעלה הזרם * כישלון טיפה למוצר כולו.
מתעלם מ-429/” ניסיון חוזר ”.
פסק הזמן של הלקוח ארוך יותר מזה של הזרם העליון (או בכלל לא).
”לטפל” טעויות עסקיות עם רטראס.
14) רשימת מימושים (0-30 יום)
0-7 ימים
לזהות נתיבים ואת האדישות שלהם.
קבע פסקי זמן (חיבור/קריאה/כלל), אפשר מגשים מינימליים (× 1) וברירת מחדל CB.
הפרד את הבריכות/המכסות (מחיצה) עבור הזרם העליון.
8-20 ימים
כולל ג 'יטר ותקציב המגש הגלובלי, התראות בשיעור חוזר.
הגדרת פליטה חריגה יותר על ההיקף, כישלון מהיר עבור תכונת low-prio.
RED + CB/Retry לוחות מחוונים, שבילים מתויגים.
21-30 ימים
פרופילי מגש קנריים (פחות ניסיונות), יום משחק ”איטי/דש”.
מדיניות מסמך: מי/מה חוזר, גבולות, יוצאים מן הכלל.
סקור את p95/p99 ופסקי זמן לפי הנתונים, לא לפי העין.
15) מדדי בגרות
ל-100% מהנתיבים יש פסקי זמן ומדיניות של מגש/לא.
התעריף החוזר משתלב בתקציב (10-15%), אין קוצים בתקריות.
CBs אש לפני הבריכה כולה נופלת; אין כישלונות מפלים.
שבילים מראים ניסיונות/גידור; 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) מסקנה
קיימות היא משמעת: פסקי זמן * נסיגה (עם ג 'יטר ותקציב) * שובר מעגל + מחיצה/מכסות ודחייה מהירה. הגדרת היקף (outlier-ejection), נתק לוחות מחוונים של RED/CB/Retry, תיקון מדיניות האידמפוטנטיות ואל תשכח את SLI העסקי. אז כישלונות קצרים יישארו בלתי נראים, ואירועים אמיתיים לא יהפכו לנפילות.