Lepšie pokusy s exponenciálnym spätným chodom a chvením
1. Prehľad
V tomto tutoriáli preskúmame, ako môžeme vylepšiť pokusy klientov pomocou dvoch rôznych stratégií: exponenciálne ustupovanie a chvenie.
2. Skúste to znova
V distribuovanom systéme môže sieťová komunikácia medzi mnohými komponentmi kedykoľvek zlyhať. Klientske aplikácie riešia tieto zlyhania implementáciouopakuje.
Predpokladajme, že máme klientsku aplikáciu, ktorá vyvoláva vzdialenú službu - PingPongService.
interface PingPongService {Volanie reťazca (String ping) vyvolá PingPongServiceException; }
Klientská aplikácia sa musí pokúsiť znova, ak PingPongService vracia a PingPongServiceException. V nasledujúcich častiach sa pozrieme na spôsoby implementácie opakovaných pokusov klientov.
3. Resilience4j Skúste to znova
V našom príklade budeme používať knižnicu Resilience4j, najmä jej modul opakovania. Budeme musieť do nášho pridať modul resilience4j-retry pom.xml:
io.github.resilience4j resilience4j-opakovať
Ak sa chcete osviežiť pri používaní pokusov, nezabudnite si prečítať nášho Sprievodcu po odolnosti4j.
4. Exponenciálne ustúpenie
Klientske aplikácie musia implementovať pokusy zodpovedne. Keď sa klienti pokúsia o neúspešné volanie bez čakania, môžu ovládnuť systéma prispievajú k ďalšiemu zhoršovaniu služieb, ktoré sú už v núdzi.
Exponenciálne backoff je bežná stratégia pre spracovanie pokusov o zlyhanie sieťových hovorov. Jednoducho povedané klienti čakajú postupne dlhšie intervaly medzi po sebe idúcimi pokusmi:
wait_interval = základňa * multiplikátor ^ n
kde,
- základňa je počiatočný interval, tj. počkajte na prvý pokus
- n je počet zlyhaní, ktoré sa vyskytli
- multiplikátor je ľubovoľný multiplikátor, ktorý je možné nahradiť ľubovoľnou vhodnou hodnotou
Týmto prístupom poskytujeme systému dýchací priestor na zotavenie z prerušovaných porúch alebo dokonca zo závažnejších problémov.
V opakovaní Resilience4j môžeme použiť exponenciálny spätný algoritmus nakonfigurovaním jeho IntervalFunction ktorý prijíma initialInterval a a multiplikátor.
The IntervalFunction sa používa mechanizmom opakovania ako funkcia spánku:
IntervalFunction intervalFn = IntervalFunction.ofExponentialBackoff (INITIAL_INTERVAL, MULTIPLIER); RetryConfig retryConfig = RetryConfig.custom () .maxAttempts (MAX_RETRIES) .intervalFunction (intervalFn) .build (); Retry retry = Retry.of ("pingpong", retryConfig); Funkcia pingPongFn = Opakovať .decorateFunction (opakovať, ping -> service.call (ping)); pingPongFn.apply ("Dobrý deň");
Poďme simulovať scenár z reálneho sveta a predpokladajme, že máme niekoľko klientov, ktorí sa dovolávajú PingPongService súčasne:
ExekútorServisní exekútori = newFixedThreadPool (NUM_CONCURRENT_CLIENTS); Zoznam úloh = nCopies (NUM_CONCURRENT_CLIENTS, () -> pingPongFn.apply ("Hello")); Exekútori.invokeAll (úlohy);
Pozrime sa na protokoly vzdialeného vyvolania pre NUM_CONCURRENT_CLIENTS rovné 4:
[závit-1] O 00: 37: 42,756 [závit-2] O 00: 37: 42,756 [závit-3] O 00: 37: 42,756 [závit-4] O 00: 37: 42,756 [závit-2] O 00: 37: 43,802 [vlákno-4] O 00: 37: 43,802 [vlákno-1] O 00: 37: 43,802 [vlákno-3] O 00: 37: 43,802 [vlákno-2] O 00: 37: 45,803 [ závit-1] o 00:37:45,803 [závit-4] o 00:37: 45,803 [závit-3] o 00:37: 45,803 [závit-2] o 00:37: 49,808 [závit-3] o 00 : 37: 49.808 [vlákno-4] O 00: 37: 49,808 [vlákno-1] O 00: 37: 49,808
Vidíme tu jasný vzor - klienti čakajú na exponenciálne rastúce intervaly, ale všetci volajú vzdialenú službu v presne rovnakom čase pri každom opakovaní (kolízii).
Riešili sme iba časť problému - vzdialenú službu netrápime opakovaním, ale namiesto rozloženia pracovného zaťaženia v čase sme striedali obdobia práce s dlhším časom nečinnosti. Toto správanie je podobné problému s hromovým stádom.
5. Predstavujeme Jitter
V našom predchádzajúcom prístupe sú čakania klienta postupne dlhšie, ale stále synchronizované. Pridanie chvenia poskytuje spôsob, ako prerušiť synchronizáciu medzi klientmi, čím sa zabráni kolíziám. V tomto prístupe pridávame do intervalov čakania náhodnosť.
wait_interval = (základňa * 2 ^ n) +/- (random_interval)
kde, random_interval sa pridá (alebo odčíta), aby sa prerušila synchronizácia medzi klientmi.
Nebudeme sa venovať mechanike výpočtu náhodného intervalu, ale náhodenie musí vyčleniť hroty na oveľa plynulejšiu distribúciu volaní klientov.
Môžeme použiť exponenciálne backoff s jitterom v Resilience4j opakovaní konfiguráciou exponenciálneho náhodného backoffu IntervalFunction ktorý zároveň prijíma a randomisationFactor:
IntervalFunction intervalFn = IntervalFunction.ofExponentialRandomBackoff (INITIAL_INTERVAL, MULTIPLIER, RANDOMIZATION_FACTOR);
Vráťme sa k nášmu scenáru z reálneho sveta a pozrime sa na protokoly vzdialeného vyvolania s chvením:
[thread-2] At 39: 21.297 [thread-4] At 39: 21.297 [thread-3] At 39: 21.297 [thread-1] At 39: 21.297 [thread-2] At 39: 21.918 [thread-3] V čase 39: 21,868 [vlákno-4] V čase 39: 22,011 [vlákno-1] V čase 39: 22,184 [vlákno-1] V čase 39: 23,086 [vlákno-5] V čase 39: 23,939 [vlákno-3] V čase 39: 24,152 [ závit-4] Pri 39: 24,977 [závit-3] Pri 39: 26,861 [závit-1] Pri 39: 28,617 [závit-4] Pri 39: 28,942 [závit-2] Pri 39: 31,039
Teraz máme oveľa lepšiu nátierku. Máme eliminoval kolízie aj čas nečinnosti a skončil s takmer konštantnou rýchlosťou volaní klientov, okrem počiatočného nárastu.
Poznámka: Pre ilustráciu sme nadhodnotili interval a v skutočných scenároch by sme mali menšie medzery.
6. Záver
V tomto tutoriáli sme preskúmali, ako môžeme vylepšiť, ako klientske aplikácie opakujú zlyhané hovory, a to rozšírením exponenciálneho backbacku s jitterom.
Zdrojový kód pre vzorky použité v tomto výučbe je k dispozícii na GitHub.