![Search icon](https://hackernoon.imgix.net/search-new.png?w=19&h=19)
Otpornost softvera se odnosi na sposobnost aplikacije da nastavi da funkcioniše glatko i pouzdano, čak i kada se suoče sa neočekivanim problemima ili kvarovima. U Fintech projektima otpornost je od posebnog značaja iz nekoliko razloga. Prvo, kompanije su obavezne da ispune regulatorne zahtjeve, a finansijski regulatori naglašavaju operativnu otpornost kako bi održali stabilnost unutar sistema. Štaviše, proliferacija digitalnih alata i oslanjanje na pružatelje usluga trećih strana izlaže Fintech kompanije pojačanim sigurnosnim prijetnjama. Otpornost također pomaže u ublažavanju rizika od prekida uzrokovanih različitim faktorima kao što su sajber prijetnje, pandemije ili geopolitički događaji, štiteći osnovne poslovne operacije i kritičnu imovinu.
Pod obrascima otpornosti razumijemo skup najboljih praksi i strategija dizajniranih da osiguraju da softver može izdržati poremećaje i održati svoje operacije. Ovi obrasci djeluju kao sigurnosne mreže, obezbjeđujući mehanizme za rukovanje greškama, upravljanje opterećenjem i oporavak od kvarova, čime se osigurava da aplikacije ostanu robusne i pouzdane u nepovoljnim uvjetima.
Najčešće strategije otpornosti uključuju pregradu, keš memoriju, rezervni, ponovni pokušaj i prekidač. U ovom članku ću ih detaljnije razmotriti, uz primjere problema koje mogu pomoći u rješavanju.
Hajde da pogledamo gornju postavku. Imamo vrlo običnu aplikaciju sa nekoliko backend-a iza nas da dobijemo neke podatke. Postoji nekoliko HTTP klijenata povezanih sa ovim backendovima. Ispostavilo se da svi oni dijele isti bazen veza! Kao i drugi resursi kao što su CPU i RAM.
Šta će se dogoditi, ako jedan od backend-a iskusi neku vrstu problema koji rezultira velikim kašnjenjem zahtjeva? Zbog velikog vremena odgovora, cijeli skup veza će biti u potpunosti zauzet zahtjevima koji čekaju odgovore od backend1. Kao rezultat toga, zahtjevi namijenjeni zdravim backend2 i backend3 neće se moći nastaviti jer je skup iscrpljen. To znači da kvar u jednom od naših backenda može uzrokovati kvar u cijeloj aplikaciji. U idealnom slučaju, želimo da samo funkcionalnost povezana s neispravnim backend-om doživi degradaciju, dok ostatak aplikacije nastavi normalno raditi.
Šta je uzorak pregrade?
Termin, Bulkhead pattern, potiče iz brodogradnje, uključuje stvaranje nekoliko izoliranih odjeljaka unutar broda. Ako dođe do curenja u jednom odeljku, on se puni vodom, ali ostali odeljci ostaju nepromenjeni. Ova izolacija sprečava da čitavo plovilo potone zbog jednog proboja.
Uzorak Bulkhead se može koristiti za izolaciju različitih tipova resursa unutar aplikacije, sprječavajući da kvar u jednom dijelu utječe na cijeli sistem. Evo kako to možemo primijeniti na naš problem:
Pretpostavimo da naši pozadinski sistemi imaju malu vjerovatnoću da pojedinačno naiđu na greške. Međutim, kada operacija uključuje paralelno ispitivanje svih ovih pozadina, svaki može nezavisno vratiti grešku. Budući da se ove greške javljaju nezavisno, ukupna vjerovatnoća greške u našoj aplikaciji je veća od vjerovatnoće greške bilo kojeg pojedinačnog backenda. Kumulativna vjerovatnoća greške može se izračunati korištenjem formule P_total=1−(1−p)^n, gdje je n broj pozadinskih sistema.
Na primjer, ako imamo deset backenda, svaki sa vjerovatnoćom greške p=0,001 (što odgovara SLA od 99,9%), rezultirajuća vjerovatnoća greške je:
P_ukupno=1−(1−0,001)^10=0,009955
To znači da naš kombinovani SLA pada na približno 99%, što ilustruje kako se ukupna pouzdanost smanjuje kada se paralelno ispituje više pozadinskih jedinica. Da bismo ublažili ovaj problem, možemo implementirati keš memoriju.
Keš memorija u memoriji služi kao bafer podataka velike brzine, pohranjujući podatke kojima se često pristupa i eliminirajući potrebu da ih svaki put preuzimate iz potencijalno sporih izvora. Budući da predmemorije pohranjene u memoriji imaju 0% šanse za grešku u odnosu na dohvaćanje podataka preko mreže, oni značajno povećavaju pouzdanost naše aplikacije. Štaviše, keširanje smanjuje mrežni promet, dodatno smanjujući mogućnost grešaka. Posljedično, korištenjem keša u memoriji, možemo postići čak nižu stopu grešaka u našoj aplikaciji u poređenju sa našim pozadinskim sistemima. Uz to, keš memorije nudi brže dohvaćanje podataka od mrežnog dohvaćanja, čime se smanjuje kašnjenje aplikacije – značajna prednost.
Za personalizirane podatke, kao što su korisnički profili ili preporuke, korištenje predmemorije u memoriji također može biti vrlo efikasno. Ali moramo osigurati da svi zahtjevi korisnika dosljedno idu na istu instancu aplikacije kako bismo koristili podatke keširane za njih, što zahtijeva ljepljive sesije. Implementacija lepljivih sesija može biti izazovna, ali za ovaj scenario nam nisu potrebni složeni mehanizmi. Manje rebalansiranje saobraćaja je prihvatljivo, tako da će stabilan algoritam za balansiranje opterećenja kao što je dosledno heširanje biti dovoljan.
Štaviše, u slučaju kvara čvora, dosljedno heširanje osigurava da samo korisnici povezani s neuspjelim čvorom budu podvrgnuti ponovnom balansiranju, minimizirajući poremećaj u sistemu. Ovaj pristup pojednostavljuje upravljanje personaliziranim kešovima i poboljšava ukupnu stabilnost i performanse naše aplikacije.
Ako su podaci koje namjeravamo keširati kritični i koriste se u svakom zahtjevu kojim naš sistem obrađuje, kao što su politike pristupa, planovi pretplate ili drugi vitalni entiteti u našem domenu – izvor ovih podataka mogao bi predstavljati značajnu tačku kvara u našem sistemu. Da bismo odgovorili na ovaj izazov, jedan pristup je potpuno repliciranje ovih podataka direktno u memoriju naše aplikacije.
U ovom scenariju, ako se obimom podataka u izvoru može upravljati, možemo pokrenuti proces preuzimanjem snimka ovih podataka na početku naše aplikacije. Nakon toga, možemo primiti događaje ažuriranja kako bismo osigurali da keširani podaci ostaju sinkronizirani s izvorom. Usvajanjem ove metode povećavamo pouzdanost pristupa ovim ključnim podacima, jer se svako preuzimanje dešava direktno iz memorije sa vjerovatnoćom greške od 0%. Uz to, preuzimanje podataka iz memorije je izuzetno brzo, čime se optimizira performanse naše aplikacije. Ova strategija efikasno ublažava rizik povezan sa oslanjanjem na eksterni izvor podataka, obezbeđujući dosledan i pouzdan pristup kritičnim informacijama za rad naše aplikacije.
Međutim, potreba za preuzimanjem podataka o pokretanju aplikacije, čime se odlaže proces pokretanja, krši jedan od principa '12-faktorske aplikacije' koji se zalaže za brzo pokretanje aplikacije. Ali, ne želimo da izgubimo prednosti korišćenja keširanja. Da bismo riješili ovu dilemu, istražimo potencijalna rješenja.
Brzo pokretanje je ključno, posebno za platforme kao što je Kubernetes, koje se oslanjaju na brzu migraciju aplikacija na različite fizičke čvorove. Na sreću, Kubernetes može upravljati aplikacijama koje se sporo pokreću koristeći funkcije kao što su probe pokretanja.
Još jedan izazov s kojim se možemo suočiti je ažuriranje konfiguracija dok aplikacija radi. Često je potrebno prilagođavanje vremena keš memorije ili vremena čekanja zahtjeva za rješavanje problema proizvodnje. Čak i ako možemo brzo implementirati ažurirane konfiguracijske datoteke u našu aplikaciju, primjena ovih promjena obično zahtijeva ponovno pokretanje. Uz produženo vrijeme pokretanja svake aplikacije, stalno ponovno pokretanje može značajno odgoditi implementaciju popravki za naše korisnike.
Da biste se pozabavili ovim, jedno rješenje je pohranjivanje konfiguracija u konkurentnu varijablu i pozadinska nit da je periodično ažurira. Međutim, određeni parametri, poput isteka HTTP zahtjeva, mogu zahtijevati ponovnu inicijalizaciju HTTP ili klijenta baze podataka kada se odgovarajuća konfiguracija promijeni, što predstavlja potencijalni izazov. Ipak, neki klijenti, poput Cassandra drajvera za Javu, podržavaju automatsko ponovno učitavanje konfiguracija, pojednostavljujući ovaj proces.
Implementacija konfiguracija koje se mogu ponovo učitati može ublažiti negativan utjecaj dugog vremena pokretanja aplikacije i ponuditi dodatne prednosti, kao što je olakšavanje implementacije zastavice funkcija. Ovaj pristup nam omogućava da održimo pouzdanost i odzivnost aplikacije uz efikasno upravljanje ažuriranjima konfiguracije.
Sada pogledajmo još jedan problem: u našem sistemu, kada se korisnički zahtjev primi i obradi slanjem upita u pozadinu ili bazu podataka, povremeno se primi odgovor na grešku umjesto očekivanih podataka. Nakon toga, naš sistem odgovara korisniku 'greškom'.
Međutim, u mnogim scenarijima može biti poželjnije prikazati blago zastarjele podatke zajedno s porukom koja ukazuje da postoji kašnjenje u osvježavanju podataka, umjesto da korisnik ima veliku crvenu poruku o grešci.
Da bismo riješili ovaj problem i poboljšali ponašanje našeg sistema, možemo implementirati Fallback obrazac. Koncept koji stoji iza ovog obrasca uključuje postojanje sekundarnog izvora podataka, koji može sadržavati podatke nižeg kvaliteta ili svježine u odnosu na primarni izvor. Ako primarni izvor podataka nije dostupan ili vraća grešku, sistem se može vratiti na dohvaćanje podataka iz ovog sekundarnog izvora, osiguravajući da se korisniku prikaže neki oblik informacija umjesto da prikaže poruku o grešci.
Ako pogledate gornju sliku, primijetit ćete sličnost između problema s kojim se sada suočavamo i onog na koji smo naišli u primjeru keša.
Da bismo to riješili, možemo razmotriti implementaciju obrasca poznatog kao ponovni pokušaj. Umjesto oslanjanja na keš memorije, sistem može biti dizajniran da automatski ponovo pošalje zahtjev u slučaju greške. Ovaj obrazac ponovnog pokušaja nudi jednostavniju alternativu i može učinkovito smanjiti vjerovatnoću grešaka u našoj aplikaciji. Za razliku od keširanja, koje često zahtijeva složene mehanizme za poništavanje predmemorije za rukovanje promjenama podataka, ponovni pokušaj neuspjelih zahtjeva je relativno jednostavan za implementaciju. Kako se poništavanje keša široko smatra jednim od najizazovnijih zadataka u softverskom inženjeringu, usvajanje strategije ponovnog pokušaja može pojednostaviti rukovanje greškama i poboljšati otpornost sistema.
Međutim, usvajanje strategije ponovnog pokušaja bez razmatranja potencijalnih posljedica može dovesti do daljih komplikacija.
Zamislimo da jedan od naših backenda doživi neuspjeh. U takvom scenariju, pokretanje ponovnih pokušaja neuspjelog backenda može rezultirati značajnim povećanjem obima prometa. Ovaj iznenadni porast prometa može preplaviti pozadinu, pogoršavajući kvar i potencijalno uzrokujući kaskadni efekat u cijelom sistemu.
Da biste se nosili s ovim izazovom, važno je dopuniti obrazac ponovnog pokušaja sa šablonom prekidača. Prekidač služi kao zaštitni mehanizam koji prati stopu greške nizvodnih usluga. Kada stopa greške premaši unaprijed definirani prag, prekidač prekida zahtjeve za pogođenu uslugu na određeno vrijeme. Tokom ovog perioda, sistem se suzdržava od slanja dodatnih zahtjeva kako bi se omogućilo vrijeme neuspjelog servisa da se oporavi. Nakon određenog intervala, prekidač oprezno dozvoljava ograničenom broju zahtjeva da prođe, provjeravajući da li se servis stabilizirao. Ako se usluga oporavi, normalan promet se postepeno vraća; u suprotnom, kolo ostaje otvoreno, nastavljajući da blokira zahtjeve dok usluga ne nastavi sa normalnim radom. Integracijom šablona prekidača zajedno sa logikom ponovnog pokušaja, možemo efikasno upravljati situacijama greške i sprečiti preopterećenje sistema tokom kvarova na pozadini.
U zaključku, implementacijom ovih obrazaca otpornosti, možemo ojačati naše aplikacije protiv hitnih situacija, održati visoku dostupnost i korisnicima pružiti besprijekorno iskustvo. Dodatno, želio bih da naglasim da je telemetrija još jedan alat koji se ne smije zanemariti kada se pruža otpornost projekta. Dobre evidencije i metrika mogu značajno poboljšati kvalitet usluga i pružiti vrijedan uvid u njihov učinak, pomažući u donošenju informiranih odluka za njihovo dalje poboljšanje.