![Search icon](https://hackernoon.imgix.net/search-new.png?w=19&h=19)
Rezistenca në softuer i referohet aftësisë së një aplikacioni për të vazhduar funksionimin pa probleme dhe të besueshme, edhe përballë çështjeve ose dështimeve të papritura. Në projektet Fintech, elasticiteti është veçanërisht i një rëndësie të madhe për disa arsye. Së pari, kompanitë janë të detyruara të përmbushin kërkesat rregullatore dhe rregullatorët financiarë theksojnë qëndrueshmërinë operacionale për të ruajtur stabilitetin brenda sistemit. Për më tepër, përhapja e mjeteve dixhitale dhe mbështetja tek ofruesit e shërbimeve të palëve të treta i ekspozon bizneset Fintech ndaj kërcënimeve të shtuara të sigurisë. Rezistenca ndihmon gjithashtu në zbutjen e rreziqeve të ndërprerjeve të shkaktuara nga faktorë të ndryshëm si kërcënimet kibernetike, pandemitë ose ngjarjet gjeopolitike, duke mbrojtur operacionet kryesore të biznesit dhe asetet kritike.
Nga modelet e elasticitetit, ne kuptojmë një sërë praktikash dhe strategjish më të mira të krijuara për të siguruar që softueri të përballojë ndërprerjet dhe të ruajë funksionimin e tij. Këto modele veprojnë si rrjeta sigurie, duke siguruar mekanizma për të trajtuar gabimet, për të menaxhuar ngarkesën dhe për të rikuperuar nga dështimet, duke siguruar kështu që aplikacionet të mbeten të qëndrueshme dhe të besueshme në kushte të pafavorshme.
Strategjitë më të zakonshme të elasticitetit përfshijnë bulkhead, cache, backback, riprovo dhe ndërprerës. Në këtë artikull, unë do t'i diskutoj ato në mënyrë më të detajuar, me shembuj të problemeve që mund të ndihmojnë në zgjidhjen e tyre.
Le të hedhim një vështrim në cilësimin e mësipërm. Ne kemi një aplikacion shumë të zakonshëm me disa backend pas nesh për të marrë disa të dhëna. Ka disa klientë HTTP të lidhur me këto mbështetëse. Rezulton se të gjithë ndajnë të njëjtin grup lidhjesh! Dhe gjithashtu burime të tjera si CPU dhe RAM.
Çfarë do të ndodhë, nëse një nga backend-et përjeton një lloj problemi që rezulton në vonesë të lartë të kërkesës? Për shkak të kohës së lartë të përgjigjes, i gjithë grupi i lidhjes do të pushtohet plotësisht nga kërkesat që presin përgjigje nga backend1. Si rezultat, kërkesat e destinuara për backend2 dhe backend3 të shëndetshëm nuk do të mund të vazhdojnë sepse grupi është shteruar. Kjo do të thotë që një dështim në një nga backend-et tona mund të shkaktojë një dështim në të gjithë aplikacionin. Në mënyrë ideale, ne duam që vetëm funksionaliteti i lidhur me prapambetjen e dështuar të përjetojë degradim, ndërsa pjesa tjetër e aplikacionit vazhdon të funksionojë normalisht.
Çfarë është modeli Bulkhead?
Termi, modeli Bulkhead, rrjedh nga ndërtimi i anijeve, ai përfshin krijimin e disa ndarjeve të izoluara brenda një anijeje. Nëse ndodh një rrjedhje në një ndarje, ajo mbushet me ujë, por ndarjet e tjera mbeten të paprekura. Ky izolim parandalon fundosjen e të gjithë anijes për shkak të një thyerjeje të vetme.
Modeli Bulkhead mund të përdoret për të izoluar lloje të ndryshme burimesh brenda një aplikacioni, duke parandaluar që një dështim në një pjesë të ndikojë në të gjithë sistemin. Ja se si mund ta zbatojmë atë në problemin tonë:
Le të supozojmë se sistemet tona mbështetëse kanë një probabilitet të ulët për të hasur gabime individualisht. Megjithatë, kur një operacion përfshin kërkimin e të gjitha këtyre backend-eve paralelisht, secili mund të kthejë në mënyrë të pavarur një gabim. Për shkak se këto gabime ndodhin në mënyrë të pavarur, probabiliteti i përgjithshëm i një gabimi në aplikacionin tonë është më i lartë se probabiliteti i gabimit të çdo fundi të vetëm. Probabiliteti i gabimit kumulativ mund të llogaritet duke përdorur formulën P_total=1−(1−p)^n, ku n është numri i sistemeve mbështetëse.
Për shembull, nëse kemi dhjetë backend, secila me një probabilitet gabimi prej p=0.001 (që korrespondon me një SLA prej 99.9%), probabiliteti i gabimit që rezulton është:
P_total=1−(1−0.001)^10=0.009955
Kjo do të thotë se SLA-ja jonë e kombinuar bie në afërsisht 99%, duke ilustruar se si zvogëlohet besueshmëria e përgjithshme kur kërkohen paralelisht disa mbështetëse. Për të zbutur këtë problem, ne mund të implementojmë një cache në memorie.
Një cache në memorie shërben si një bufer i të dhënave me shpejtësi të lartë, duke ruajtur të dhënat e aksesuara shpesh dhe duke eliminuar nevojën për t'i marrë ato nga burime potencialisht të ngadalta çdo herë. Meqenëse cache-et e ruajtura në memorie kanë një shans 0% gabimi në krahasim me marrjen e të dhënave përmes rrjetit, ato rrisin ndjeshëm besueshmërinë e aplikacionit tonë. Për më tepër, caching redukton trafikun e rrjetit, duke ulur më tej mundësinë e gabimeve. Rrjedhimisht, duke shfrytëzuar një cache në memorie, ne mund të arrijmë një shkallë gabimi edhe më të ulët në aplikacionin tonë në krahasim me sistemet tona backend. Për më tepër, cache-të në memorie ofrojnë rikthim më të shpejtë të të dhënave sesa marrja e bazuar në rrjet, duke reduktuar kështu vonesën e aplikacionit - një avantazh i dukshëm.
Për të dhëna të personalizuara, të tilla si profilet e përdoruesve ose rekomandimet, përdorimi i memories në memorie mund të jetë gjithashtu shumë efektiv. Por ne duhet të sigurohemi që të gjitha kërkesat nga një përdorues të shkojnë vazhdimisht në të njëjtin shembull aplikacioni për të përdorur të dhënat e ruajtura në memorie për ta, gjë që kërkon seanca ngjitëse. Zbatimi i seancave ngjitëse mund të jetë sfidues, por për këtë skenar, ne nuk kemi nevojë për mekanizma komplekse. Ribalancimi i vogël i trafikut është i pranueshëm, kështu që do të mjaftojë një algoritëm i qëndrueshëm i balancimit të ngarkesës si hashimi i qëndrueshëm.
Për më tepër, në rast të dështimit të nyjeve, hashimi i qëndrueshëm siguron që vetëm përdoruesit e lidhur me nyjen e dështuar t'i nënshtrohen ribalancimit, duke minimizuar ndërprerjen e sistemit. Kjo qasje thjeshton menaxhimin e cache-ve të personalizuara dhe rrit stabilitetin dhe performancën e përgjithshme të aplikacionit tonë.
Nëse të dhënat që synojmë të ruajmë në memorie janë kritike dhe përdoren në çdo kërkesë që trajton sistemi ynë, si p.sh. politikat e aksesit, planet e abonimit ose entitete të tjera jetike në domenin tonë - burimi i këtyre të dhënave mund të përbëjë një pikë të rëndësishme dështimi në sistemin tonë. Për të adresuar këtë sfidë, një qasje është që këto të dhëna të përsëriten plotësisht drejtpërdrejt në kujtesën e aplikacionit tonë.
Në këtë skenar, nëse vëllimi i të dhënave në burim është i menaxhueshëm, ne mund ta nisim procesin duke shkarkuar një fotografi të këtyre të dhënave në fillim të aplikacionit tonë. Më pas, ne mund të marrim ngjarje të përditësimeve për të siguruar që të dhënat e ruajtura në memorie të mbeten të sinkronizuara me burimin. Duke adoptuar këtë metodë, ne rrisim besueshmërinë e aksesit në këto të dhëna thelbësore, pasi çdo rikthim ndodh drejtpërdrejt nga memorja me një probabilitet gabimi 0%. Për më tepër, marrja e të dhënave nga memorja është jashtëzakonisht e shpejtë, duke optimizuar kështu performancën e aplikacionit tonë. Kjo strategji zbut në mënyrë efektive rrezikun që lidhet me mbështetjen në një burim të jashtëm të dhënash, duke siguruar qasje të qëndrueshme dhe të besueshme në informacionin kritik për funksionimin e aplikacionit tonë.
Megjithatë, nevoja për të shkarkuar të dhënat për fillimin e aplikacionit, duke vonuar kështu procesin e nisjes, shkel një nga parimet e 'aplikacionit me 12 faktorë' që mbron për fillimin e shpejtë të aplikacionit. Por, ne nuk duam të humbasim përfitimet e përdorimit të caching. Për të adresuar këtë dilemë, le të shqyrtojmë zgjidhjet e mundshme.
Fillimi i shpejtë është thelbësor, veçanërisht për platformat si Kubernetes, të cilat mbështeten në migrimin e shpejtë të aplikacioneve në nyje të ndryshme fizike. Për fat të mirë, Kubernetes mund të menaxhojë aplikacionet me fillim të ngadaltë duke përdorur veçori si hetimet e nisjes.
Një sfidë tjetër me të cilën mund të përballemi është përditësimi i konfigurimeve gjatë ekzekutimit të aplikacionit. Shpesh, rregullimi i kohës së cache-it ose afatet e kërkesave janë të nevojshme për të zgjidhur çështjet e prodhimit. Edhe nëse mund të vendosim shpejt skedarët e konfigurimit të përditësuar në aplikacionin tonë, zbatimi i këtyre ndryshimeve zakonisht kërkon një rinisje. Me kohën e zgjatur të nisjes së çdo aplikacioni, një rinisje e vazhdueshme mund të vonojë ndjeshëm vendosjen e rregullimeve për përdoruesit tanë.
Për të trajtuar këtë, një zgjidhje është ruajtja e konfigurimeve në një ndryshore të njëkohshme dhe azhurnimi periodik i një filli të sfondit. Megjithatë, disa parametra, si p.sh. afatet e kërkesave HTTP, mund të kërkojnë rifillimin e klientëve të HTTP ose bazës së të dhënave kur ndryshon konfigurimi përkatës, duke paraqitur një sfidë të mundshme. Megjithatë, disa klientë, si shoferi Cassandra për Java, mbështesin rimbushjen automatike të konfigurimeve, duke e thjeshtuar këtë proces.
Zbatimi i konfigurimeve të ringarkueshme mund të zbusë ndikimin negativ të kohërave të gjata të fillimit të aplikacionit dhe të ofrojë përfitime shtesë, të tilla si lehtësimi i zbatimit të flamurit të veçorive. Kjo qasje na mundëson të ruajmë besueshmërinë dhe reagimin e aplikacionit ndërsa menaxhojmë në mënyrë efikase përditësimet e konfigurimit.
Tani le t'i hedhim një vështrim një problemi tjetër: në sistemin tonë, kur një kërkesë përdoruesi merret dhe përpunohet duke dërguar një pyetje në një bazë ose bazë të dhënash, herë pas here, në vend të të dhënave të pritura merret një përgjigje gabimi. Më pas, sistemi ynë i përgjigjet përdoruesit me një 'gabim'.
Megjithatë, në shumë skenarë, mund të jetë më e preferueshme të shfaqen të dhënat paksa të vjetruara së bashku me një mesazh që tregon se ka një vonesë të rifreskimit të të dhënave, në vend që t'i lini përdoruesit një mesazh gabimi të madh të kuq.
Për të adresuar këtë problem dhe për të përmirësuar sjelljen e sistemit tonë, ne mund të zbatojmë modelin e kthimit. Koncepti pas këtij modeli përfshin të kesh një burim të dhënash dytësore, i cili mund të përmbajë të dhëna me cilësi ose freski më të ulët në krahasim me burimin parësor. Nëse burimi kryesor i të dhënave nuk është i disponueshëm ose kthen një gabim, sistemi mund të kthehet në marrjen e të dhënave nga ky burim dytësor, duke siguruar që një formë informacioni t'i paraqitet përdoruesit në vend që të shfaqë një mesazh gabimi.
Nëse shikoni foton e mësipërme, do të vini re një ngjashmëri midis problemit me të cilin po përballemi tani dhe atij që hasëm me shembullin e cache-it.
Për ta zgjidhur atë, ne mund të konsiderojmë zbatimin e një modeli të njohur si riprovim. Në vend që të mbështetet në cache, sistemi mund të projektohet për të ridërguar automatikisht kërkesën në rast të një gabimi. Ky model i riprovës ofron një alternativë më të thjeshtë dhe mund të zvogëlojë në mënyrë efektive gjasat e gabimeve në aplikacionin tonë. Ndryshe nga caching, i cili shpesh kërkon mekanizma komplekse të anulimit të memories për të trajtuar ndryshimet e të dhënave, riprovimi i kërkesave të dështuara është relativisht i thjeshtë për t'u zbatuar. Meqenëse anulimi i cache-it konsiderohet gjerësisht si një nga detyrat më sfiduese në inxhinierinë e softuerit, miratimi i një strategjie të riprovës mund të thjeshtojë trajtimin e gabimeve dhe të përmirësojë elasticitetin e sistemit.
Megjithatë, miratimi i një strategjie të riprovës pa marrë parasysh pasojat e mundshme mund të çojë në komplikime të mëtejshme.
Le të imagjinojmë që një nga mbështetësit tanë të përjetojë një dështim. Në një skenar të tillë, fillimi i riprovave për pjesën e pasme të dështuar mund të rezultojë në një rritje të konsiderueshme të volumit të trafikut. Kjo rritje e papritur e trafikut mund të mposht pjesën e pasme, duke përkeqësuar dështimin dhe duke shkaktuar potencialisht një efekt kaskadë në të gjithë sistemin.
Për të përballuar këtë sfidë, është e rëndësishme të plotësoni modelin e riprovës me modelin e ndërprerësit. Ndërprerësi shërben si një mekanizëm mbrojtës që monitoron shkallën e gabimit të shërbimeve në rrjedhën e poshtme. Kur shkalla e gabimit tejkalon një prag të paracaktuar, ndërprerësi ndërpret kërkesat për shërbimin e prekur për një kohëzgjatje të caktuar. Gjatë kësaj periudhe, sistemi përmbahet nga dërgimi i kërkesave shtesë për të lejuar rikuperimin e kohës së shërbimit të dështuar. Pas intervalit të caktuar, ndërprerësi lejon me kujdes kalimin e një numri të kufizuar kërkesash, duke verifikuar nëse shërbimi është stabilizuar. Nëse shërbimi është rikuperuar, qarkullimi normal rikthehet gradualisht; përndryshe, qarku mbetet i hapur, duke vazhduar të bllokojë kërkesat derisa shërbimi të rifillojë funksionimin normal. Duke integruar modelin e ndërprerësit së bashku me logjikën e riprovës, ne mund të menaxhojmë në mënyrë efektive situatat e gabimit dhe të parandalojmë mbingarkimin e sistemit gjatë dështimeve të prapavijës.
Si përfundim, duke zbatuar këto modele elasticiteti, ne mund të forcojmë aplikacionet tona kundër emergjencave, të ruajmë disponueshmërinë e lartë dhe t'u ofrojmë përdoruesve një përvojë të qetë. Për më tepër, do të doja të theksoja se telemetria është një mjet tjetër që nuk duhet të neglizhohet kur sigurohet elasticiteti i projektit. Regjistrat dhe metrikat e mira mund të rrisin ndjeshëm cilësinë e shërbimeve dhe të ofrojnë njohuri të vlefshme për performancën e tyre, duke ndihmuar në marrjen e vendimeve të informuara për t'i përmirësuar ato më tej.