Bedre forsøg med eksponentiel backoff og jitter

1. Oversigt

I denne vejledning undersøger vi, hvordan vi kan forbedre klientforsøg med to forskellige strategier: eksponentiel backoff og jitter.

2. Prøv igen

I et distribueret system kan netværkskommunikation mellem de mange komponenter mislykkes når som helst. Klientapplikationer håndterer disse fejl ved implementeringprøver igen.

Lad os antage, at vi har en klientapplikation, der påberåber en fjerntjeneste - den PingPongService.

interface PingPongService {String call (String ping) kaster PingPongServiceException; }

Klientapplikationen skal prøve igen, hvis PingPongService returnerer a PingPongServiceException. I de følgende sektioner ser vi på måder at implementere klientforsøg på.

3. Resilience4j Prøv igen

For vores eksempel bruger vi Resilience4j-biblioteket, især dets forsøgsmodul. Vi bliver nødt til at tilføje resilience4j-retry-modulet til vores pom.xml:

 io.github.resilience4j resilience4j-prøv igen 

For en opdatering af brug af forsøg, så glem ikke at tjekke vores Guide til Resilience4j.

4. Eksponentiel tilbagetrækning

Klientapplikationer skal implementere forsøg ansvarligt. Når klienter prøver igen på mislykkede opkald uden at vente, kan de overvælde systemet, og bidrage til yderligere nedbrydning af den tjeneste, der allerede er i nød.

Eksponentiel backoff er en fælles strategi til håndtering af forsøg på mislykkede netværksopkald. Enkelt sagt klienterne venter gradvis længere intervaller mellem på hinanden følgende forsøg:

wait_interval = base * multiplikator ^ n 

hvor,

  • grundlag er det indledende interval, dvs. vent på det første forsøg igen
  • n er antallet af fejl, der er opstået
  • multiplikator er en vilkårlig multiplikator, der kan erstattes med en hvilken som helst passende værdi

Med denne tilgang giver vi et åndedrætsrum til systemet for at komme sig efter intermitterende fejl eller endnu mere alvorlige problemer.

Vi kan bruge den eksponentielle backoff-algoritme i Resilience4j forsøg igen ved at konfigurere dens IntervalFunction der accepterer en initialInterval og en multiplikator.

Det IntervalFunction bruges af forsøgsmekanismen som en søvnfunktion:

IntervalFunction intervalFn = IntervalFunction.ofExponentialBackoff (INITIAL_INTERVAL, MULTIPLIER); RetryConfig retryConfig = RetryConfig.custom () .maxAttempts (MAX_RETRIES) .intervalFunction (intervalFn) .build (); Prøv igen prøv igen = Retry.of ("pingpong", retryConfig); Funktion pingPongFn = Prøv igen .decorateFunction (prøv igen, ping -> service.call (ping)); pingPongFn.apply ("Hej"); 

Lad os simulere et virkeligt scenarie og antage, at vi har flere klienter, der påberåber sig PingPongService samtidigt:

ExecutorService eksekutører = newFixedThreadPool (NUM_CONCURRENT_CLIENTS); Listeopgaver = nKopier (NUM_CONCURRENT_CLIENTS, () -> pingPongFn.apply ("Hej")); executors.invokeAll (opgaver); 

Lad os se på fjernkaldelseslogfiler for NUM_CONCURRENT_CLIENTS lig med 4:

[tråd-1] Ved 00: 37: 42.756 [tråd-2] Ved 00: 37: 42.756 [tråd-3] Ved 00: 37: 42.756 [tråd-4] Ved 00: 37: 42.756 [tråd-2] Ved 00: 37: 43.802 [tråd-4] Kl. 00: 37: 43.802 [tråd-1] Kl. 00: 37: 43.802 [tråd-3] Kl. 00: 37: 43.802 [tråd-2] Kl. 00: 37: 45.803 [ tråd-1] Kl. 00: 37: 45.803 [tråd-4] Kl. 00: 37: 45.803 [tråd-3] Kl. 00: 37: 45.803 [tråd-2] Kl. 00: 37: 49.808 [tråd-3] Kl. : 37: 49.808 [tråd-4] Kl. 00: 37: 49.808 [tråd-1] Kl. 00: 37: 49.808 

Vi kan se et klart mønster her - klienterne venter på eksponentielt voksende intervaller, men alle ringer til fjerntjenesten nøjagtigt på samme tid ved hvert forsøg (kollisioner).

Vi har kun taget fat på en del af problemet - vi hamrer ikke fjerntjenesten med forsøg længere, men i stedet for at sprede arbejdsbyrden over tid, har vi afbrudt perioder med arbejde med mere inaktiv tid. Denne adfærd er beslægtet med Thundering Herd Problem.

5. Introduktion til Jitter

I vores tidligere tilgang er klientens ventetid gradvis længere, men stadig synkroniseret. Tilføjelse af jitter giver en måde at bryde synkroniseringen på tværs af klienterne og derved undgå kollisioner. I denne tilgang tilføjer vi tilfældighed til ventningsintervallerne.

Vent_interval = (base * 2 ^ n) +/- (tilfældig_interval) 

hvor, tilfældigt_interval tilføjes (eller trækkes fra) for at bryde synkroniseringen på tværs af klienter.

Vi går ikke ind i mekanikken ved beregning af det tilfældige interval, men randomisering skal rumme spidserne ud til en meget glattere fordeling af klientopkald.

Vi kan bruge den eksponentielle backoff med jitter i Resilience4j prøv igen ved at konfigurere en eksponentiel random backoff IntervalFunction der også accepterer en randomizationFactor:

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

Lad os vende tilbage til vores virkelige scenarie og se på fjernsøgningslogfiler med jitter:

[tråd-2] Ved 39: 21.297 [tråd-4] Ved 39: 21.297 [tråd-3] Ved 39: 21.297 [tråd-1] Ved 39: 21.297 [tråd-2] Ved 39: 21.918 [tråd-3] Ved 39: 21.868 [tråd-4] Ved 39: 22.011 [tråd-1] Ved 39: 22.184 [tråd-1] Ved 39: 23.086 [tråd-5] Ved 39: 23.939 [tråd-3] Ved 39: 24.152 [ tråd-4] Ved 39: 24.977 [tråd-3] Ved 39: 26.861 [tråd-1] Ved 39: 28.617 [tråd-4] Ved 39: 28.942 [tråd-2] Ved 39: 31.039

Nu har vi en meget bedre spredning. Vi har elimineret både kollisioner og inaktiv tid og ender med en næsten konstant hastighed af klientopkald, udelukker den oprindelige bølge.

Bemærk: Vi har overvurderet intervallet til illustration, og i virkelige scenarier ville vi have mindre huller.

6. Konklusion

I denne vejledning har vi undersøgt, hvordan vi kan forbedre, hvordan klientapplikationer prøver igen mislykkede opkald ved at udvide eksponentiel backoff med jitter.

Kildekoden til de eksempler, der bruges i vejledningen, er tilgængelig på GitHub.


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