A szoftverek rugalmassága az alkalmazás azon képességét jelenti, hogy továbbra is zökkenőmentesen és megbízhatóan működjön, még váratlan problémák vagy hibák esetén is. A Fintech projektekben a reziliencia több okból is kiemelten fontos. Először is, a vállalatok kötelesek megfelelni a szabályozási követelményeknek, a pénzügyi szabályozók pedig a működési rugalmasságot hangsúlyozzák a rendszeren belüli stabilitás fenntartása érdekében. Ezenkívül a digitális eszközök elterjedése és a külső szolgáltatókra való támaszkodás fokozott biztonsági fenyegetésnek teszi ki a Fintech vállalkozásokat. A reziliencia emellett segít csökkenteni a különféle tényezők – például kiberfenyegetések, világjárványok vagy geopolitikai események – által okozott leállások kockázatát, megóvva az alapvető üzleti műveleteket és a kritikus eszközöket.
A rugalmassági minták alatt bevált gyakorlatokat és stratégiákat értünk, amelyek célja annak biztosítása, hogy a szoftverek ellenálljanak a zavaroknak és fenntartsák működésüket. Ezek a minták biztonsági hálóként működnek, és mechanizmusokat biztosítanak a hibák kezelésére, a terhelés kezelésére és a hibák utáni helyreállításra, ezáltal biztosítva, hogy az alkalmazások robusztusak és megbízhatóak maradjanak kedvezőtlen körülmények között is.
A leggyakoribb rugalmassági stratégiák közé tartozik a válaszfal, a gyorsítótár, a tartalék, az újrapróbálkozás és a megszakító. Ebben a cikkben részletesebben tárgyalom őket, és példákat mutatok be azokra a problémákra, amelyek segíthetnek megoldani.
Vessünk egy pillantást a fenti beállításra. Van egy nagyon hétköznapi alkalmazásunk, több háttérrel a hátunk mögött, ahonnan adatot kaphatunk. Számos HTTP-kliens csatlakozik ezekhez a háttérrendszerekhez. Kiderült, hogy mindegyik ugyanazon a kapcsolati készleten osztozik! És egyéb erőforrások is, mint a CPU és a RAM.
Mi történik, ha az egyik háttérprogram valamilyen problémát tapasztal, ami magas kérési késést eredményez? A magas válaszidő miatt a teljes kapcsolatkészletet teljesen elfoglalják a backend1-től válaszra váró kérések. Ennek eredményeként az egészséges backend2-nek és backend3-nak szánt kérelmek nem fognak tudni folytatni, mert a készlet kimerült. Ez azt jelenti, hogy az egyik háttérrendszerünk meghibásodása az egész alkalmazásban hibát okozhat. Ideális esetben azt szeretnénk, ha csak a hibás háttérrendszerhez kapcsolódó funkciók romlása tapasztalható, míg az alkalmazás többi része továbbra is a megszokott módon működik.
Mi az a válaszfalminta?
A válaszfalminta kifejezés a hajóépítésből származik, és magában foglalja több elkülönített rekesz létrehozását egy hajón belül. Ha az egyik rekeszben szivárgás lép fel, az megtelik vízzel, de a többi rekesz érintetlen marad. Ez az elszigetelés megakadályozza, hogy az egész hajó elsüllyedjen egyetlen törés miatt.
A Bulkhead minta felhasználható különféle típusú erőforrások elkülönítésére egy alkalmazáson belül, megakadályozva, hogy egy rész meghibásodása az egész rendszert érintse. A következőképpen alkalmazhatjuk a problémánkra:
Tegyük fel, hogy a háttérrendszereinkben kicsi a valószínűsége annak, hogy egyenként hibákat találjanak. Ha azonban egy művelet mindezen háttérprogramok párhuzamos lekérdezését foglalja magában, mindegyik egymástól függetlenül hibát adhat vissza. Mivel ezek a hibák egymástól függetlenül fordulnak elő, az alkalmazásunkban előforduló hiba általános valószínűsége nagyobb, mint bármely egyetlen háttérprogram hibavalószínűsége. A kumulatív hibavalószínűség a P_total=1−(1−p)^n képlettel számítható, ahol n a háttérrendszerek száma.
Például, ha tíz háttérprogramunk van, amelyek mindegyikének a hibavalószínűsége p=0,001 (ami 99,9%-os SLA-nak felel meg), a kapott hiba valószínűsége a következő:
P_total=1-(1-0,001)^10=0,009955
Ez azt jelenti, hogy a kombinált SLA-nk körülbelül 99%-ra csökken, ami azt mutatja, hogy az általános megbízhatóság hogyan csökken, ha több háttérprogramot párhuzamosan lekérdezünk. A probléma enyhítése érdekében a memórián belüli gyorsítótárat alkalmazhatunk.
A memórián belüli gyorsítótár nagy sebességű adatpufferként szolgál, tárolja a gyakran használt adatokat, és szükségtelenné teszi, hogy minden alkalommal lekérje azokat potenciálisan lassú forrásokból. Mivel a memóriában tárolt gyorsítótárak hibalehetősége 0% a hálózaton keresztüli adatlekéréshez képest, jelentősen növelik alkalmazásunk megbízhatóságát. Ezenkívül a gyorsítótárazás csökkenti a hálózati forgalmat, tovább csökkentve a hibák esélyét. Következésképpen a memórián belüli gyorsítótár használatával még alacsonyabb hibaarányt érhetünk el az alkalmazásunkban, mint a háttérrendszereinkben. Ezenkívül a memórián belüli gyorsítótárak gyorsabb adatlekérést tesznek lehetővé, mint a hálózati alapú lekérés, ezáltal csökkentve az alkalmazások várakozási idejét – ez jelentős előny.
A személyre szabott adatok, például felhasználói profilok vagy ajánlások esetében a memórián belüli gyorsítótárak használata szintén nagyon hatékony lehet. Biztosítanunk kell azonban, hogy a felhasználóktól érkező összes kérés következetesen ugyanahhoz az alkalmazáspéldányhoz kerüljön, hogy felhasználja a számukra gyorsítótárazott adatokat, amihez ragadós munkamenetekre van szükség. A ragadós munkamenetek megvalósítása kihívást jelenthet, de ehhez a forgatókönyvhöz nincs szükségünk bonyolult mechanizmusokra. Kisebb forgalom-kiegyenlítés elfogadható, ezért elegendő egy olyan stabil terheléselosztási algoritmus, mint a következetes hash.
Sőt, csomópont meghibásodása esetén a konzisztens kivonatolás biztosítja, hogy csak a meghibásodott csomóponthoz tartozó felhasználók menjenek át az egyensúlyozáson, minimálisra csökkentve a rendszer megzavarását. Ez a megközelítés leegyszerűsíti a személyre szabott gyorsítótárak kezelését, és javítja alkalmazásunk általános stabilitását és teljesítményét.
Ha a gyorsítótárba helyezni kívánt adatok kritikusak, és a rendszerünk által kezelt minden kérésben felhasználják őket, például hozzáférési szabályzatokban, előfizetési tervekben vagy más, a tartományunkon belüli létfontosságú entitásokban, akkor ezen adatok forrása jelentős hibapontot jelenthet rendszerünkben. Ennek a kihívásnak a megoldása érdekében az egyik megközelítés az, hogy ezeket az adatokat közvetlenül az alkalmazásunk memóriájába replikáljuk.
Ebben a forgatókönyvben, ha a forrásban lévő adatok mennyisége kezelhető, elindíthatjuk a folyamatot az adatok pillanatképének letöltésével az alkalmazásunk elején. Ezt követően frissítési eseményeket kaphatunk annak biztosítására, hogy a gyorsítótárazott adatok szinkronban maradjanak a forrással. Ennek a módszernek az elfogadásával növeljük a kulcsfontosságú adatok elérésének megbízhatóságát, mivel minden visszakeresés közvetlenül a memóriából történik 0%-os hiba valószínűséggel. Ezenkívül az adatok memóriából való lekérése rendkívül gyors, ezáltal optimalizálva az alkalmazásunk teljesítményét. Ez a stratégia hatékonyan mérsékli a külső adatforrásra támaszkodó kockázatokat, biztosítva az alkalmazásunk működéséhez szükséges kritikus információkhoz való következetes és megbízható hozzáférést.
Az alkalmazásindítási adatok letöltésének szükségessége azonban, ezáltal késlelteti az indítási folyamatot, sérti a „12-faktoros alkalmazás” egyik alapelvét, amely az alkalmazás gyors indítását hirdeti. De nem akarjuk elveszíteni a gyorsítótárazás előnyeit. Ennek a dilemmának a megoldásához vizsgáljuk meg a lehetséges megoldásokat.
A gyors indítás kulcsfontosságú, különösen az olyan platformok esetében, mint a Kubernetes, amelyek az alkalmazások különböző fizikai csomópontokra történő gyors migrációján alapulnak. Szerencsére a Kubernetes képes kezelni a lassan induló alkalmazásokat olyan funkciókkal, mint például az indítási próbák.
Egy másik kihívás, amellyel szembesülhetünk, a konfigurációk frissítése az alkalmazás futása közben. A termelési problémák megoldásához gyakran szükség van a gyorsítótár időinek módosítására vagy a kérések időkorlátjaira. Még ha gyorsan telepíteni is tudjuk a frissített konfigurációs fájlokat az alkalmazásunkban, ezeknek a változtatásoknak az alkalmazása általában újraindítást igényel. Az egyes alkalmazások meghosszabbított indítási idejével a folyamatos újraindítás jelentősen késleltetheti a javítások telepítését a felhasználók számára.
Ennek megoldására az egyik megoldás az, hogy a konfigurációkat egy párhuzamos változóban tároljuk, és egy háttérszálat rendszeresen frissítjük. Bizonyos paraméterek, például a HTTP-kérés időtúllépései azonban megkövetelhetik a HTTP- vagy adatbázis-kliensek újrainicializálását, amikor a megfelelő konfiguráció megváltozik, ami potenciális kihívást jelenthet. Ennek ellenére egyes kliensek, mint például a Cassandra Java-illesztőprogram, támogatják a konfigurációk automatikus újratöltését, leegyszerűsítve ezt a folyamatot.
Az újratölthető konfigurációk megvalósítása mérsékelheti az alkalmazások hosszú indítási idejének negatív hatását, és további előnyöket kínál, például megkönnyíti a funkciójelző megvalósítását. Ez a megközelítés lehetővé teszi számunkra, hogy fenntartsuk az alkalmazások megbízhatóságát és válaszkészségét, miközben hatékonyan kezeljük a konfigurációs frissítéseket.
Most pedig nézzünk meg egy másik problémát: rendszerünkben, amikor egy felhasználói kérés érkezik és egy háttérrendszerbe vagy adatbázisba küldött lekérdezéssel dolgozunk fel, esetenként hibaüzenet érkezik a várt adatok helyett. Ezt követően rendszerünk „hibával” válaszol a felhasználónak.
Sok esetben azonban előnyösebb lehet kissé elavult adatokat megjeleníteni az adatfrissítési késleltetést jelző üzenettel együtt, ahelyett, hogy nagy piros hibaüzenetet hagyna a felhasználónak.
A probléma megoldása és rendszerünk viselkedésének javítása érdekében megvalósíthatjuk a tartalék mintát. A minta mögött meghúzódó koncepció egy másodlagos adatforrást tartalmaz, amely az elsődleges forráshoz képest gyengébb minőségű vagy frissebb adatokat tartalmazhat. Ha az elsődleges adatforrás nem elérhető, vagy hibát ad vissza, a rendszer visszaállhat az adatok lekérésére ebből a másodlagos forrásból, biztosítva, hogy hibaüzenet helyett valamilyen információ kerüljön a felhasználó elé.
Ha megnézi a fenti képet, hasonlóságot fog észrevenni a mostani probléma és a gyorsítótár-példa kapcsán tapasztalt probléma között.
Ennek megoldásához fontolóra vehetjük egy újrapróbálkozásként ismert minta megvalósítását. A gyorsítótárak helyett a rendszer úgy tervezhető, hogy hiba esetén automatikusan újraküldje a kérést. Ez az újrapróbálkozási minta egyszerűbb alternatívát kínál, és hatékonyan csökkentheti az alkalmazásunkban előforduló hibák valószínűségét. A gyorsítótárazástól eltérően, amely gyakran összetett gyorsítótár-érvénytelenítési mechanizmusokat igényel az adatváltozások kezeléséhez, a sikertelen kérések újrapróbálkozása viszonylag egyszerűen megvalósítható. Mivel a gyorsítótár érvénytelenítését széles körben a szoftverfejlesztés egyik legnagyobb kihívást jelentő feladatának tekintik, az újrapróbálkozási stratégia alkalmazása egyszerűsítheti a hibakezelést és javíthatja a rendszer rugalmasságát.
Az újrapróbálkozási stratégia elfogadása azonban a lehetséges következmények figyelembevétele nélkül további bonyodalmakhoz vezethet.
Képzeljük el, hogy az egyik háttérprogramunk kudarcot szenved. Ebben a forgatókönyvben a hibás háttérrendszerhez való újrapróbálkozások a forgalom jelentős növekedését eredményezhetik. Ez a hirtelen megugrott forgalom túlterhelheti a háttérrendszert, súlyosbíthatja a hibát, és potenciálisan kaszkádhatást okozhat a rendszerben.
Ahhoz, hogy megbirkózzunk ezzel a kihívással, fontos, hogy az újrapróbálkozási mintát kiegészítsük a megszakító mintával. A megszakító védelmi mechanizmusként szolgál, amely figyeli a downstream szolgáltatások hibaarányát. Ha a hibaarány túllép egy előre meghatározott küszöbértéket, a megszakító meghatározott időtartamra megszakítja az érintett szolgáltatáshoz intézett kéréseket. Ebben az időszakban a rendszer tartózkodik a további kérések küldésétől, hogy lehetővé tegye a meghibásodott szolgáltatás helyreállításához szükséges időt. A megadott időköz után a megszakító óvatosan enged korlátozott számú kérést átmenni, ellenőrizve, hogy a szolgáltatás stabilizálódott-e. Ha a szolgáltatás helyreállt, fokozatosan helyreáll a normál forgalom; ellenkező esetben az áramkör nyitva marad, és továbbra is blokkolja a kéréseket, amíg a szolgáltatás vissza nem tér a normál működésre. A megszakító mintának az újrapróbálkozási logika mellé integrálásával hatékonyan kezelhetjük a hibahelyzeteket és megelőzhetjük a rendszer túlterhelését a háttérrendszer meghibásodásai során.
Összefoglalva, ezeknek a rugalmassági mintáknak a megvalósításával megerősíthetjük alkalmazásainkat a vészhelyzetekkel szemben, fenntarthatjuk a magas rendelkezésre állást, és zökkenőmentes élményt nyújthatunk a felhasználóknak. Ezenkívül szeretném hangsúlyozni, hogy a telemetria egy újabb eszköz, amelyet nem szabad figyelmen kívül hagyni a projekt rugalmasságának biztosítása során. A jó naplók és mérőszámok jelentősen javíthatják a szolgáltatások minőségét, és értékes betekintést nyújthatnak a teljesítményükbe, és segítenek megalapozott döntéseket hozni a szolgáltatások további javítása érdekében.