נסיונות טובים יותר עם Backoff ו- Jitter אקספוננציאלי

1. סקירה כללית

במדריך זה נחקור כיצד אנו יכולים לשפר את נסיונות הלקוח בשתי אסטרטגיות שונות: ביטול מעריכי וריצוד.

2. נסה שוב

במערכת מבוזרת, תקשורת רשת בין המרכיבים הרבים יכולה להיכשל בכל עת. יישומי לקוח מתמודדים עם כשלים אלה באמצעות יישוםמנסה שוב.

נניח שיש לנו יישום לקוח שמפעיל שירות מרחוק - ה- PingPongService.

ממשק PingPongService {שיחת מחרוזת (מחרוזת פינג) זורק PingPongServiceException; }

יישום הלקוח חייב לנסות שוב אם PingPongService מחזירה א PingPongServiceException. בחלקים הבאים נבחן דרכים ליישום ניסיונות חוזרים של הלקוח.

3. Resilience4j נסה שוב

לדוגמא שלנו, נשתמש בספריית Resilience4j, במיוחד במודול שלה. נצטרך להוסיף את המודול resilience4j-retry ל- שלנו pom.xml:

 io.github.resilience4j resilience4j- נסה שוב 

לקבלת רענון על שימוש בניסיון חוזר, אל תשכח לבדוק את המדריך לחוסן 4j.

4. כיבוי אקספוננציאלי

יישומי לקוח חייבים ליישם ניסיונות אחראיים. כאשר לקוחות מנסים שוב להתקשר כושל מבלי להמתין, הם עלולים להציף את המערכת, ותורמים להמשך השפלה של השירות שכבר נמצא במצוקה.

כיבוי אקספוננציאלי הוא אסטרטגיה נפוצה לטיפול בניסיונות חוזרים של שיחות רשת כושלות. במילים פשוטות, הלקוחות ממתינים בהדרגה מרווחים ארוכים יותר בין נסיונות רצופים:

wait_interval = בסיס * מכפיל ^ n 

איפה,

  • בסיס הוא המרווח הראשוני, כלומר המתן לניסיון חוזר הראשון
  • נ הוא מספר הכשלים שהתרחשו
  • מַכפִּיל הוא מכפיל שרירותי שניתן להחליף אותו בכל ערך מתאים

בגישה זו אנו מספקים מרחב נשימה למערכת להתאושש מכשלים לסירוגין, או אפילו מבעיות חמורות יותר.

אנו יכולים להשתמש באלגוריתם backoff אקספוננציאלי בניסיון חוזר Resilience4j על ידי הגדרת התצורה שלו IntervalFunction שמקבל Initial Interval ו מַכפִּיל.

ה IntervalFunction משמש על ידי מנגנון הניסיון מחדש כפונקציית שינה:

IntervalFunction intervalFn = IntervalFunction.ofExponentialBackoff (INITIAL_INTERVAL, MULTIPLIER); RetryConfig retryConfig = RetryConfig.custom () .maxAttempts (MAX_RETRIES) .intervalFunction (intervalFn) .build (); נסה שוב שוב = נסה שוב. Of ("פינג פונג", נסה שוב קונפיג); פונקציה pingPongFn = נסה שוב .decorateFunction (נסה שוב, פינג -> service.call (פינג)); pingPongFn.apply ("שלום"); 

בואו לדמות תרחיש בעולם האמיתי, ולהניח שיש לנו כמה לקוחות שמפעילים את ה- PingPongService במקביל:

ExecutorService executors = newFixedThreadPool (NUM_CONCURRENT_CLIENTS); משימות רשימה = n עותקים (NUM_CONCURRENT_CLIENTS, () -> pingPongFn.apply ("שלום")); executors.invokeAll (משימות); 

בואו נסתכל ביומני הפנייה המרוחקים עבור NUM_CONCURRENT_CLIENTS שווה ל -4:

[חוט -1] בשעה 00: 37: 42.756 [חוט -2] בשעה 00: 37: 42.756 [חוט -3] בשעה 00: 37: 42.756 [חוט -4] בשעה 00: 37: 42.756 [חוט -2] בשעה 00: 37: 43.802 [thread-4] בשעה 00: 37: 43.802 [thread-1] בשעה 00: 37: 43.802 [thread-3] בשעה 00: 37: 43.802 [thread-2] בשעה 00: 37: 45.803 [ חוט -1] בשעה 00: 37: 45.803 [חוט -4] בשעה 00: 37: 45.803 [חוט -3] בשעה 00: 37: 45.803 [חוט -2] בשעה 00: 37: 49.808 [חוט -3] בשעה 00 : 37: 49.808 [thread-4] בשעה 00: 37: 49.808 [thread-1] בשעה 00: 37: 49.808 

אנו יכולים לראות דפוס ברור כאן - הלקוחות ממתינים למרווחים שהולכים וגדלים באופן אקספוננציאלי, אך כולם מתקשרים לשירות מרחוק בדיוק באותו זמן בכל ניסיון חוזר (התנגשויות).

טיפלנו רק בחלק מהבעיה - אנחנו כבר לא מרעישים את השירות המרוחק עם ניסיון חוזר, אך במקום לפזר את עומס העבודה לאורך זמן, יש לנו תקופות עבודה משולבות עם זמן סרק יותר. התנהגות זו דומה לבעיית העדר הרועמת.

5. היכרות עם ג'יטר

בגישה הקודמת שלנו הלקוח ממתין בהדרגה אך עדיין מסונכרן. הוספת ריצוד מספקת דרך לשבור את הסנכרון בין הלקוחות ובכך למנוע התנגשויות. בגישה זו אנו מוסיפים אקראיות למרווחי ההמתנה.

wait_interval = (base * 2 ^ n) +/- (interval_interval) 

איפה, רווח אקראי מתווסף (או מופחת) כדי לשבור את הסנכרון בין לקוחות.

לא ניכנס למכניקה של חישוב המרווח האקראי, אך רנדומיזציה חייבת לרווח את הדוקרנים להפצה חלקה הרבה יותר של שיחות לקוחות.

אנו יכולים להשתמש בגיבוי מעריכי עם ריצוד ב Resilience4j לנסות שוב על ידי הגדרת הגבה אקראית אקספוננציאלית IntervalFunction שמקבל גם א randomizationFactor:

IntervalFunction intervalFn = IntervalFunction.ofExponentialRandomBackoff (INITIAL_INTERVAL, MULTIPLIER, RANDOMIZATION_FACTOR); 

בואו נחזור לתרחיש שלנו בעולם האמיתי, ונסתכל ביומני הפנייה המרוחקים בעזרת ריצוד:

[חוט -2] בשעה 39: 21.297 [חוט -4] בשעה 39: 21.297 [חוט -3] בשעה 39: 21.297 [חוט -1] בשעה 39: 21.297 [חוט -2] בשעה 39: 21.918 [חוט -3] בשעה 39: 21.868 [חוט -4] בשעה 39: 22.011 [חוט -1] בשעה 39: 22.184 [חוט -1] בשעה 39: 23.086 [חוט -5] בשעה 39: 23.939 [חוט -3] בשעה 39: 24.152 [ חוט -4] בשעה 39: 24.977 [חוט -3] בשעה 39: 26.861 [חוט -1] בשעה 39: 28.617 [חוט -4] בשעה 39: 28.942 [חוט -2] בשעה 39: 31.039

עכשיו יש לנו התפשטות הרבה יותר טובה. יש לנו ביטל את ההתנגשויות ואת זמן הסרק, ובסופו של דבר בשיעור כמעט קבוע של שיחות לקוחות, וחוסמת את הזינוק הראשוני.

הערה: הגזמנו את מרווח ההמחשה, ובתרחישים בעולם האמיתי יהיו לנו פערים קטנים יותר.

6. מסקנה

במדריך זה, בדקנו כיצד אנו יכולים לשפר את אופן יישומי הלקוח לנסות שוב שיחות כושלות על ידי הגדלת חיזור מעריכי עם ריצוד.

קוד המקור של הדוגמאות המשמשות במדריך זמין באתר GitHub.


$config[zx-auto] not found$config[zx-overlay] not found