Betere nieuwe pogingen met exponentiële backoff en jitter

1. Overzicht

In deze tutorial onderzoeken we hoe we nieuwe pogingen van klanten kunnen verbeteren met twee verschillende strategieën: exponentiële backoff en jitter.

2. Probeer het opnieuw

In een gedistribueerd systeem kan de netwerkcommunicatie tussen de vele componenten op elk moment mislukken. Clienttoepassingen lossen deze fouten op door ze te implementerenpogingen.

Laten we aannemen dat we een clienttoepassing hebben die een service op afstand aanroept - de PingPongService.

interface PingPongService {String-oproep (String-ping) genereert PingPongServiceException; }

De clienttoepassing moet het opnieuw proberen als het PingPongService geeft een terug PingPongServiceException. In de volgende secties zullen we kijken naar manieren om nieuwe pogingen van klanten te implementeren.

3. Resilience4j Opnieuw proberen

Voor ons voorbeeld gebruiken we de Resilience4j-bibliotheek, met name de retry-module. We zullen de resilience4j-retry-module moeten toevoegen aan onze pom.xml:

 io.github.resilience4j resilience4j-retry 

Vergeet niet om onze Guide to Resilience4j te raadplegen voor een opfriscursus over het gebruik van nieuwe pogingen.

4. Exponentiële terugval

Clienttoepassingen moeten nieuwe pogingen op verantwoorde wijze implementeren. Als clients mislukte oproepen opnieuw proberen zonder te wachten, kunnen ze het systeem overweldigen, en bijdragen aan een verdere verslechtering van de service die al in nood verkeert.

Exponentiële uitstel is een veelgebruikte strategie voor het afhandelen van nieuwe pogingen van mislukte netwerkoproepen. In simpele termen, de clients wachten steeds langere intervallen tussen opeenvolgende pogingen:

wait_interval = basis * vermenigvuldiger ^ n 

waar,

  • baseren is het initiële interval, dwz wacht op de eerste nieuwe poging
  • n is het aantal storingen dat is opgetreden
  • vermenigvuldiger is een willekeurige vermenigvuldiger die kan worden vervangen door elke geschikte waarde

Met deze aanpak bieden we het systeem een ​​adempauze om te herstellen van periodieke storingen of zelfs ernstiger problemen.

We kunnen het exponentiële backoff-algoritme gebruiken in Resilience4j retry door zijn IntervalFunctie dat accepteert een initialInterval en een vermenigvuldiger.

De IntervalFunctie wordt door het mechanisme voor opnieuw proberen gebruikt als slaapfunctie:

IntervalFunction intervalFn = IntervalFunction.ofExponentialBackoff (INITIAL_INTERVAL, MULTIPLIER); RetryConfig retryConfig = RetryConfig.custom () .maxAttempts (MAX_RETRIES) .intervalFunction (intervalFn) .build (); Retry retry = Retry.of ("pingpong", retryConfig); Functie pingPongFn = Retry .decorateFunction (retry, ping -> service.call (ping)); pingPongFn.apply ("Hallo"); 

Laten we een realistisch scenario simuleren en aannemen dat we verschillende clients hebben die het PingPongService gelijktijdig:

ExecutorService executors = newFixedThreadPool (NUM_CONCURRENT_CLIENTS); Lijst met taken = nCopies (NUM_CONCURRENT_CLIENTS, () -> pingPongFn.apply ("Hallo")); executors.invokeAll (taken); 

Laten we eens kijken naar de externe aanroeplogboeken voor NUM_CONCURRENT_CLIENTS gelijk aan 4:

[draad-1] Om 00: 37: 42.756 [draad-2] Om 00: 37: 42.756 [draad-3] Om 00: 37: 42.756 [draad-4] Om 00: 37: 42.756 [draad-2] Om 00: 37: 43.802 [draad-4] Om 00: 37: 43.802 [draad-1] Om 00: 37: 43.802 [draad-3] Om 00: 37: 43.802 [draad-2] Om 00: 37: 45.803 [ draad-1] Om 00: 37: 45.803 [draad-4] Om 00: 37: 45.803 [draad-3] Om 00: 37: 45.803 [draad-2] Om 00: 37: 49.808 [draad-3] Om 00 : 37: 49.808 [draad-4] Om 00: 37: 49.808 [draad-1] Om 00: 37: 49.808 

We kunnen hier een duidelijk patroon zien: de klanten wachten op exponentieel groeiende intervallen, maar ze bellen allemaal de service op afstand op precies hetzelfde moment bij elke nieuwe poging (botsingen).

We hebben slechts een deel van het probleem aangepakt - we hameren de externe service niet meer met nieuwe pogingen, maar in plaats van de werklast over de tijd te spreiden, hebben we werkperiodes afgewisseld met meer inactieve tijd. Dit gedrag is verwant aan het probleem van de donderende kudde.

5. Introductie van Jitter

In onze vorige benadering zijn de wachttijden van de cliënt geleidelijk langer maar nog steeds gesynchroniseerd. Door jitter toe te voegen, kan de synchronisatie tussen de clients worden verbroken, waardoor botsingen worden vermeden. Bij deze benadering voegen we willekeur toe aan de wachtintervallen.

wait_interval = (basis * 2 ^ n) +/- (willekeurig_interval) 

waar, random_interval wordt toegevoegd (of afgetrokken) om de synchronisatie tussen clients te verbreken.

We zullen niet ingaan op de mechanica van het berekenen van het willekeurige interval, maar randomisatie moet de pieken verdelen om een ​​veel soepelere verdeling van clientoproepen te bewerkstelligen.

We kunnen de exponentiële backoff met jitter gebruiken in Resilience4j retry door een exponentiële willekeurige backoff te configureren IntervalFunctie dat accepteert ook een randomizationFactor:

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

Laten we terugkeren naar ons real-world scenario, en kijken naar de logs van aanroep op afstand met jitter:

[draad-2] Om 39: 21.297 [draad-4] Om 39: 21.297 [draad-3] Om 39: 21.297 [draad-1] Om 39: 21.297 [draad-2] Om 39: 21.918 [draad-3] Op 39: 21.868 [draad-4] Op 39: 22.011 [draad-1] Op 39: 22.184 [draad-1] Op 39: 23.086 [draad-5] Op 39: 23.939 [draad-3] Op 39: 24.152 [ draad-4] Op 39: 24.977 [draad-3] Op 39: 26.861 [draad-1] Op 39: 28.617 [draad-4] Op 39: 28.942 [draad-2] Op 39: 31.039

Nu hebben we een veel betere spreiding. We hebben elimineerde zowel botsingen als inactieve tijd, en eindigde met een bijna constant aantal klantoproepen, behoudens de aanvankelijke stijging.

Opmerking: we hebben het interval ter illustratie overdreven, en in real-world scenario's zouden we kleinere hiaten hebben.

6. Conclusie

In deze zelfstudie hebben we onderzocht hoe we kunnen verbeteren hoe clienttoepassingen mislukte oproepen opnieuw proberen door exponentiële backoff te vergroten met jitter.

De broncode voor de voorbeelden die in de zelfstudie worden gebruikt, is beschikbaar op GitHub.