Bolji pokušaji s eksponencijalnim padom i podrhtavanjem

1. Pregled

U ovom uputstvu istražit ćemo kako možemo poboljšati pokušaje klijenta pomoću dvije različite strategije: eksponencijalni odboj i podrhtavanje.

2. Pokušajte ponovo

U distribuiranom sustavu mrežna komunikacija između brojnih komponenata može u bilo kojem trenutku zakazati. Klijentske aplikacije rješavaju ove kvarove primjenomponovni pokušaji.

Pretpostavimo da imamo klijentsku aplikaciju koja poziva udaljenu uslugu - PingPongService.

sučelje PingPongService {String poziv (String ping) baca PingPongServiceException; }

Klijentska aplikacija mora pokušati ponovno ako PingPongService vraća a PingPongServiceException. U sljedećim odjeljcima potražit ćemo načine za implementaciju pokušaja klijenta.

3. Otpornost4j Pokušaj ponovo

Za naš primjer koristit ćemo knjižnicu Resilience4j, posebno njezin modul za ponovni pokušaj. Morat ćemo dodati modul resilience4j-retry u naš pom.xml:

 io.github.resilience4j elastičnost4j-ponovni pokušaj 

Za osvježavanje upotrebe ponovnih pokušaja, ne zaboravite pogledati naš Vodič za elastičnost4j.

4. Eksponencijalni Backoff

Klijentske aplikacije moraju odgovorno primijeniti pokušaje. Kad klijenti pokušaju neuspjele pozive bez čekanja, mogli bi preplaviti sustavi pridonijeti daljnjoj degradaciji usluge koja je već u nevolji.

Eksponencijalni backoff uobičajena je strategija za rukovanje pokušajima neuspjelih mrežnih poziva. Jednostavno rečeno, klijenti postupno čekaju sve duže intervale između uzastopnih pokušaja:

interval čekanja = baza * množitelj ^ n 

gdje,

  • baza je početni interval, tj. pričekajte prvi ponovni pokušaj
  • n je broj kvarova koji su se dogodili
  • multiplikator proizvoljan je množitelj koji se može zamijeniti bilo kojom prikladnom vrijednošću

Ovim pristupom sustavu osiguravamo prostor za disanje za oporavak od povremenih kvarova ili još ozbiljnijih problema.

Možemo koristiti eksponencijalni internetski algoritam u ponovnom pokušaju Resilience4j konfiguriranjem njegovog IntervalFunction koja prihvaća početniInterval i a multiplikator.

The IntervalFunction koristi mehanizam ponovnog pokušaja kao funkciju spavanja:

IntervalFunction intervalFn = IntervalFunction.ofExponentialBackoff (INITIAL_INTERVAL, MULTIPLIER); RetryConfig retryConfig = RetryConfig.custom () .maxAttempts (MAX_RETRIES) .intervalFunction (intervalFn) .build (); Pokušaj ponovo pokušaj = Pokušaj ponovo.of ("pingpong", retryConfig); Funkcija pingPongFn = Pokušaj .decorateFunction (pokušaj, ping -> service.call (ping)); pingPongFn.apply ("Zdravo"); 

Simulirajmo stvarni scenarij i pretpostavimo da imamo nekoliko klijenata koji se pozivaju na PingPongService istovremeno:

Izvršitelji ExecutorService = newFixedThreadPool (NUM_CONCURRENT_CLIENTS); Popis zadataka = nKopije (NUM_CONCURRENT_CLIENTS, () -> pingPongFn.apply ("Pozdrav")); izvršitelji.invokeAll (zadaci); 

Pogledajmo udaljene zapisnike poziva NUM_CONCURRENT_CLIENTS jednako 4:

[nit-1] U 00: 37: 42,756 [nit-2] U 00: 37: 42,756 [nit-3] U 00: 37: 42,756 [nit-4] U 00: 37: 42,756 [nit-2] U 00: 37: 43,802 [nit-4] U 00: 37: 43,802 [nit-1] U 00: 37: 43,802 [nit-3] U 00: 37: 43,802 [nit-2] U 00: 37: 45,803 [ nit-1] U 00: 37: 45.803 [nit-4] U 00: 37: 45.803 [nit-3] U 00: 37: 45.803 [nit-2] U 00: 37: 49.808 [nit-3] U 00 : 37: 49.808 [nit-4] U 00: 37: 49.808 [nit-1] U 00: 37: 49.808 

Ovdje možemo vidjeti jasan obrazac - klijenti čekaju eksponencijalno rastuće intervale, ali svi oni pozivaju udaljenu uslugu točno u isto vrijeme pri svakom ponovnom pokušaju (sudara).

Riješili smo samo dio problema - daljinsku uslugu više ne čekivamo s ponovnim pokušajima, ali umjesto da se radno opterećenje širi s vremenom, mi smo isprekidali razdoblja rada s više praznih sati. Ovo ponašanje slično je problemu s grmljavinskim stadom.

5. Predstavljamo Jitter

U našem prethodnom pristupu, klijentova čekanja su sve dulja, ali još uvijek sinkronizirana. Dodavanje podrhtavanja pruža način da se prekine sinkronizacija među klijentima, izbjegavajući tako sudare. U ovom pristupu intervalima čekanja dodajemo slučajnost.

interval čekanja = (baza * 2 ^ n) +/- (slučajni_interval) 

gdje, slučajni_interval dodaje se (ili oduzima) kako bi se prekinula sinkronizacija između klijenata.

Nećemo ulaziti u mehaniku izračunavanja slučajnog intervala, ali randomizacija mora razmaknuti šiljke do puno uglađenije raspodjele poziva klijenata.

Možemo koristiti eksponencijalni backkoff s podrhtavanjem u Resilience4j ponovnom pokušaju konfiguriranjem eksponencijalnog slučajnog backkoffa IntervalFunction koja također prihvaća a faktor randomizacije:

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

Vratimo se našem stvarnom scenariju i pogledajmo udaljene zapisnike poziva s podrhtavanjem:

[nit-2] U 39: 21.297 [nit-4] U 39: 21.297 [nit-3] U 39: 21.297 [nit-1] U 39: 21.297 [nit-2] U 39: 21.918 [nit-3] U 39: 21.868 [nit-4] U 39: 22.011 [nit-1] U 39: 22.184 [nit-1] U 39: 23.086 [nit-5] U 39: 23.939 [nit-3] U 39: 24.152 [ nit-4] U 39: 24,977 [nit-3] U 39: 26,861 [nit-1] U 39: 28,617 [nit-4] U 39: 28,942 [nit-2] U 39: 31,039

Sad imamo puno bolji namaz. Imamo eliminirao je i kolizije i vrijeme mirovanja, a na kraju je imao gotovo konstantnu brzinu poziva klijenata, zabranjujući početni val.

Napomena: Pretjerali smo u intervalima za ilustraciju, a u stvarnim scenarijima imali bismo manje praznine.

6. Zaključak

U ovom uputstvu istražili smo kako možemo poboljšati način na koji klijentske aplikacije ponovno pokušavaju neuspjele pozive povećavajući eksponencijalni odboj s podrhtavanjem.

Izvorni kod za uzorke korištene u vodiču dostupan je na GitHub-u.


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