Pohodlné architektonické vzory

Čau Habr!

Vo svetle aktuálnych udalostí v súvislosti s koronavírusom začalo byť množstvo internetových služieb čoraz viac zaťažovaných. Napríklad, Jeden z maloobchodných reťazcov v Spojenom kráľovstve jednoducho zastavil svoju online objednávkovú stránku., pretože nebola dostatočná kapacita. A nie vždy je možné zrýchliť server jednoduchým pridaním výkonnejšieho vybavenia, ale požiadavky klientov musia byť spracované (alebo prejdú ku konkurencii).

V tomto článku budem stručne hovoriť o populárnych postupoch, ktoré vám umožnia vytvoriť rýchlu službu odolnú voči chybám. Z možných schém rozvoja som však vybral len tie, ktoré aktuálne sú jednoduché použitie. Pre každú položku máte buď pripravené knižnice, alebo máte možnosť vyriešiť problém pomocou cloudovej platformy.

Horizontálne škálovanie

Najjednoduchší a najznámejší bod. Bežne sú najbežnejšie dve schémy rozloženia zaťaženia horizontálne a vertikálne. V prvom prípade umožníte službám bežať paralelne, čím rozložíte záťaž medzi ne. V druhom objednávate výkonnejšie servery alebo optimalizujete kód.

Napríklad vezmem abstraktné cloudové úložisko súborov, to znamená nejaký analóg OwnCloud, OneDrive atď.

Štandardný obrázok takéhoto obvodu je uvedený nižšie, ale iba demonštruje zložitosť systému. Musíme predsa nejako zosynchronizovať služby. Čo sa stane, ak používateľ uloží súbor z tabletu a potom si ho chce pozrieť z telefónu?

Pohodlné architektonické vzory
Rozdiel medzi prístupmi: pri vertikálnom škálovaní sme pripravení zvýšiť výkon uzlov a v horizontálnom škálovaní sme pripravení pridať nové uzly na rozloženie zaťaženia.

CQRS

Command Query Oddelenie zodpovednosti Pomerne dôležitý vzor, ​​pretože umožňuje rôznym klientom nielen sa pripojiť k rôznym službám, ale aj prijímať rovnaké toky udalostí. Jeho výhody nie sú pri jednoduchej aplikácii až také zrejmé, no pri vyťaženej službe je mimoriadne dôležité (a jednoduché). Jeho podstata: prichádzajúce a odchádzajúce dátové toky by sa nemali pretínať. To znamená, že nemôžete odoslať požiadavku a očakávať odpoveď, namiesto toho pošlete požiadavku službe A, ale dostanete odpoveď od služby B.

Prvým bonusom tohto prístupu je schopnosť prerušiť spojenie (v širšom zmysle slova) pri vykonávaní dlhej požiadavky. Zoberme si napríklad viac-menej štandardnú postupnosť:

  1. Klient odoslal požiadavku na server.
  2. Server začal dlho spracovávať.
  3. Server odpovedal klientovi s výsledkom.

Predstavme si, že v bode 2 bolo spojenie prerušené (alebo sa sieť znova pripojila, alebo používateľ prešiel na inú stránku, čím sa spojenie prerušilo). V tomto prípade bude pre server ťažké poslať používateľovi odpoveď s informáciami o tom, čo presne bolo spracované. Pri použití CQRS sa bude sekvencia mierne líšiť:

  1. Klient sa prihlásil na odber aktualizácií.
  2. Klient odoslal požiadavku na server.
  3. Server odpovedal „žiadosť prijatá“.
  4. Server odpovedal s výsledkom cez kanál z bodu „1“.

Pohodlné architektonické vzory

Ako vidíte, schéma je trochu zložitejšia. Navyše tu chýba intuitívny prístup typu požiadavka-odpoveď. Ako však vidíte, prerušenie pripojenia počas spracovania požiadavky nepovedie k chybe. Navyše, ak je v skutočnosti používateľ pripojený k službe z viacerých zariadení (napríklad z mobilného telefónu a z tabletu), môžete sa uistiť, že odpoveď príde na obe zariadenia.

Zaujímavé je, že kód na spracovanie prichádzajúcich správ sa stáva rovnaký (nie 100%) ako pre udalosti, ktoré ovplyvnil samotný klient, tak aj pre iné udalosti, vrátane udalostí od iných klientov.

V skutočnosti však získame ďalší bonus vďaka tomu, že jednosmerné prúdenie sa dá zvládnuť funkčným štýlom (pomocou RX a podobne). A to je už vážne plus, pretože v podstate môže byť aplikácia úplne reaktívna a tiež pomocou funkčného prístupu. V prípade tukových programov to môže výrazne ušetriť rozvojové a podporné zdroje.

Ak tento prístup skombinujeme s horizontálnym škálovaním, potom ako bonus získame možnosť odosielať požiadavky na jeden server a prijímať odpovede z iného. Klient si tak môže vybrať službu, ktorá mu vyhovuje, a systém vo vnútri bude stále schopný správne spracovávať udalosti.

Event Sourcing

Ako viete, jednou z hlavných čŕt distribuovaného systému je absencia spoločného času, spoločnej kritickej sekcie. Pre jeden proces môžete urobiť synchronizáciu (na rovnakých mutexoch), v rámci ktorej máte istotu, že nikto iný tento kód nespúšťa. To je však nebezpečné pre distribuovaný systém, pretože si to bude vyžadovať réžiu a tiež zabije všetku krásu škálovania - všetky komponenty budú stále čakať na jeden.

Odtiaľto dostávame dôležitý fakt – rýchly distribuovaný systém sa nedá synchronizovať, pretože potom znížime výkon. Na druhej strane často potrebujeme určitú konzistenciu medzi komponentmi. A na to môžete použiť prístup s prípadná konzistencia, kde je zaručené, že ak po určitú dobu po poslednej aktualizácii nedôjde k žiadnym zmenám údajov („nakoniec“), všetky dopyty vrátia poslednú aktualizovanú hodnotu.

Je dôležité pochopiť, že pre klasické databázy sa pomerne často používa silná konzistencia, kde má každý uzol rovnaké informácie (to sa často dosiahne v prípade, keď sa transakcia považuje za zavedenú až po odpovedi druhého servera). Vďaka stupňom izolácie sú tu určité relaxácie, ale všeobecná myšlienka zostáva rovnaká - môžete žiť v úplne harmonizovanom svete.

Vráťme sa však k pôvodnej úlohe. Ak časť systému môže byť postavená s prípadná konzistencia, potom môžeme zostaviť nasledujúci diagram.

Pohodlné architektonické vzory

Dôležité vlastnosti tohto prístupu:

  • Každá prichádzajúca požiadavka je umiestnená v jednom rade.
  • Počas spracovania požiadavky môže služba zaradiť úlohy aj do iných frontov.
  • Každá prichádzajúca udalosť má identifikátor (ktorý je potrebný na deduplikáciu).
  • Poradie ideologicky funguje podľa schémy „iba pripojiť“. Nemôžete z nej odstrániť prvky ani ich usporiadať.
  • Fronta funguje podľa schémy FIFO (ospravedlňujeme sa za tautológiu). Ak potrebujete vykonať paralelné vykonávanie, v jednej fáze by ste mali presunúť objekty do rôznych frontov.

Pripomínam, že zvažujeme prípad online ukladania súborov. V tomto prípade bude systém vyzerať asi takto:

Pohodlné architektonické vzory

Je dôležité, aby služby v diagrame nevyhnutne neznamenali samostatný server. Dokonca aj proces môže byť rovnaký. Ďalšia vec je dôležitá: ideologicky sú tieto veci oddelené tak, že horizontálne škálovanie sa dá ľahko aplikovať.

A pre dvoch používateľov bude diagram vyzerať takto (služby určené pre rôznych používateľov sú označené rôznymi farbami):

Pohodlné architektonické vzory

Bonusy z takejto kombinácie:

  • Služby spracovania informácií sú oddelené. Oddelené sú aj rady. Ak potrebujeme zvýšiť priepustnosť systému, stačí spustiť viac služieb na viacerých serveroch.
  • Keď dostaneme informácie od používateľa, nemusíme čakať, kým sa údaje úplne uložia. Naopak, stačí odpovedať „ok“ a potom postupne začať pracovať. Fronta zároveň vyhladzuje špičky, pretože pridanie nového objektu prebieha rýchlo a používateľ nemusí čakať na úplný prechod celým cyklom.
  • Ako príklad som pridal službu deduplikácie, ktorá sa pokúša zlúčiť identické súbory. Ak to v 1% prípadov funguje dlhodobo, klient si to takmer nevšimne (viď vyššie), čo je veľké plus, keďže sa od nás už nevyžaduje XNUMX% rýchlosť a spoľahlivosť.

Nevýhody sú však okamžite viditeľné:

  • Náš systém stratil prísnu konzistenciu. To znamená, že ak sa napríklad prihlásite na odber rôznych služieb, teoreticky môžete získať iný stav (keďže jedna zo služieb nemusí mať čas na prijatie upozornenia z interného frontu). Ďalším dôsledkom je, že systém teraz nemá spoločný čas. To znamená, že napríklad nie je možné zoradiť všetky udalosti jednoducho podľa času príchodu, pretože hodiny medzi servermi nemusia byť synchrónne (navyše rovnaký čas na dvoch serveroch je utópia).
  • Žiadne udalosti teraz nemožno jednoducho vrátiť späť (ako by sa to dalo urobiť s databázou). Namiesto toho musíte pridať novú udalosť − kompenzačná udalosť, ktorý zmení posledný stav na požadovaný. Ako príklad z podobnej oblasti: bez prepísania histórie (čo je v niektorých prípadoch zlé) nemôžete vrátiť odovzdanie v git, ale môžete urobiť špeciálne rollback commit, ktorá v podstate len vracia starý stav. Chybný commit aj rollback však zostanú v histórii.
  • Schéma údajov sa môže meniť od vydania k vydaniu, ale staré udalosti už nebude možné aktualizovať na nový štandard (keďže udalosti sa v zásade nedajú zmeniť).

Ako môžete vidieť, Event Sourcing funguje dobre s CQRS. Navyše implementácia systému s efektívnymi a pohodlnými frontami, ale bez oddelenia dátových tokov, je už sama o sebe náročná, pretože budete musieť pridať synchronizačné body, ktoré neutralizujú celý pozitívny efekt front. Pri aplikácii oboch prístupov naraz je potrebné mierne upraviť programový kód. V našom prípade pri odosielaní súboru na server príde odpoveď iba „ok“, čo znamená iba to, že „operácia pridania súboru bola uložená“. Formálne to neznamená, že údaje sú už dostupné na iných zariadeniach (napríklad služba deduplikácie môže prebudovať index). Po určitom čase však klient dostane upozornenie v štýle „súbor X bol uložený“.

Ako výsledok:

  • Počet stavov odosielania súborov sa zvyšuje: namiesto klasického „súbor odoslaný“ dostávame dva: „súbor bol pridaný do fronty na serveri“ a „súbor bol uložený do úložiska“. To druhé znamená, že iné zariadenia už môžu začať prijímať súbor (prispôsobené skutočnosti, že fronty pracujú rôznymi rýchlosťami).
  • Vzhľadom na skutočnosť, že informácie o odoslaní teraz prichádzajú cez rôzne kanály, musíme prísť s riešeniami na získanie stavu spracovania súboru. V dôsledku toho: na rozdiel od klasickej požiadavky-odpovede môže byť klient počas spracovania súboru reštartovaný, ale samotný stav tohto spracovania bude správny. Okrem toho táto položka funguje v podstate po vybalení z krabice. V dôsledku toho sme teraz tolerantnejší k zlyhaniam.

Črepovanie

Ako je uvedené vyššie, systémy na získavanie udalostí nemajú prísnu konzistenciu. To znamená, že môžeme používať niekoľko úložísk bez akejkoľvek synchronizácie medzi nimi. Keď sa priblížime k nášmu problému, môžeme:

  • Oddeľte súbory podľa typu. Napríklad je možné dekódovať obrázky/videá a zvoliť efektívnejší formát.
  • Oddeľte účty podľa krajiny. Kvôli mnohým zákonom to môže byť potrebné, ale táto architektonická schéma poskytuje takúto príležitosť automaticky

Pohodlné architektonické vzory

Ak chcete prenášať dáta z jedného úložiska do druhého, štandardné prostriedky už nestačia. Bohužiaľ, v tomto prípade musíte zastaviť front, vykonať migráciu a potom ju spustiť. Vo všeobecnom prípade nie je možné dáta prenášať „za behu“, ak je však front udalostí úplne uložený a máte snímky predchádzajúcich stavov uloženia, môžeme udalosti prehrať nasledovne:

  • V zdroji udalosti má každá udalosť svoj vlastný identifikátor (v ideálnom prípade neklesajúci). To znamená, že do úložiska môžeme pridať pole – id posledného spracovaného prvku.
  • Front duplikujeme, aby sa všetky udalosti dali spracovať pre niekoľko nezávislých úložísk (prvé je to, v ktorom sú už uložené dáta a druhé je nové, no stále prázdne). Druhá fronta, samozrejme, ešte nie je spracovaná.
  • Spustíme druhý front (to znamená, že začneme prehrávať udalosti).
  • Keď je nový front relatívne prázdny (to znamená, že priemerný časový rozdiel medzi pridaním prvku a jeho získaním je prijateľný), môžete začať prepínať čítačky na nové úložisko.

Ako vidíte, v našom systéme sme nemali a stále nemáme prísnu konzistentnosť. Existuje iba prípadná konzistentnosť, to znamená záruka, že udalosti sa spracujú v rovnakom poradí (ale možno s rôznym oneskorením). A pomocou toho môžeme relatívne ľahko prenášať dáta bez zastavenia systému na druhú stranu zemegule.

Ak teda pokračujeme v našom príklade o online ukladaní súborov, takáto architektúra nám už poskytuje množstvo bonusov:

  • Objekty môžeme dynamicky posúvať bližšie k používateľom. Týmto spôsobom môžete zlepšiť kvalitu služieb.
  • Niektoré údaje môžeme uchovávať v rámci spoločností. Napríklad podnikoví používatelia často vyžadujú, aby boli ich údaje uložené v kontrolovaných dátových centrách (aby sa predišlo úniku dát). Prostredníctvom shardingu to môžeme jednoducho podporiť. A úloha je ešte jednoduchšia, ak má zákazník kompatibilný cloud (napr. Vlastné hosťovanie Azure).
  • A najdôležitejšie je, že to robiť nemusíme. Koniec koncov, na začiatok by sme boli celkom spokojní s jedným úložiskom pre všetky účty (aby sme mohli rýchlo začať pracovať). A kľúčovou vlastnosťou tohto systému je, že hoci je rozšíriteľný, v počiatočnej fáze je celkom jednoduchý. Jednoducho nemusíte okamžite písať kód, ktorý funguje s miliónom samostatných nezávislých front atď. V prípade potreby sa to dá urobiť v budúcnosti.

Hosting statického obsahu

Tento bod sa môže zdať celkom zrejmý, ale pre viac-menej štandardne načítanú aplikáciu je stále potrebný. Jeho podstata je jednoduchá: všetok statický obsah nie je distribuovaný z rovnakého servera, na ktorom sa nachádza aplikácia, ale zo špeciálnych serverov určených špeciálne na túto úlohu. V dôsledku toho sa tieto operácie vykonávajú rýchlejšie (podmienený nginx obsluhuje súbory rýchlejšie a lacnejšie ako server Java). Plus architektúra CDN (Content Delivery Network) nám umožňuje umiestniť naše súbory bližšie ku koncovým používateľom, čo má pozitívny vplyv na pohodlie práce so službou.

Najjednoduchším a najštandardnejším príkladom statického obsahu je súbor skriptov a obrázkov pre webovú stránku. S nimi je všetko jednoduché - sú vopred známe, potom sa archív nahrá na servery CDN, odkiaľ sa distribuuje koncovým používateľom.

V skutočnosti však pre statický obsah môžete použiť prístup trochu podobný architektúre lambda. Vráťme sa k našej úlohe (online úložisko súborov), v rámci ktorej potrebujeme distribuovať súbory používateľom. Najjednoduchším riešením je vytvoriť službu, ktorá na každú požiadavku používateľa vykoná všetky potrebné kontroly (autorizáciu atď.) a následne stiahne súbor priamo z nášho úložiska. Hlavnou nevýhodou tohto prístupu je, že statický obsah (a súbor s určitou revíziou je v skutočnosti statický obsah) distribuuje rovnaký server, ktorý obsahuje obchodnú logiku. Namiesto toho môžete vytvoriť nasledujúci diagram:

  • Server poskytuje adresu URL na stiahnutie. Môže mať tvar file_id + kľúč, kde kľúč je mini-digitálny podpis, ktorý dáva právo na prístup k zdroju na nasledujúcich XNUMX hodín.
  • Súbor je distribuovaný jednoduchým nginx s nasledujúcimi možnosťami:
    • Ukladanie obsahu do vyrovnávacej pamäte. Keďže táto služba môže byť umiestnená na samostatnom serveri, nechali sme si rezervu do budúcnosti s možnosťou ukladať všetky najnovšie stiahnuté súbory na disk.
    • Kontrola kľúča v čase vytvárania spojenia
  • Voliteľné: spracovanie streamovaného obsahu. Napríklad, ak komprimujeme všetky súbory v službe, potom môžeme urobiť rozbalenie priamo v tomto module. V dôsledku toho: Operácie IO sa vykonávajú tam, kde patria. Archivátor v jazyku Java ľahko pridelí veľa pamäte navyše, ale prepísanie služby s obchodnou logikou do podmienok Rust/C++ môže byť tiež neúčinné. V našom prípade sa využívajú rôzne procesy (alebo aj služby), a preto vieme celkom efektívne oddeliť obchodnú logiku a IO operácie.

Pohodlné architektonické vzory

Táto schéma nie je veľmi podobná distribúcii statického obsahu (keďže niekam neodovzdávame celý statický balík), ale v skutočnosti sa tento prístup týka práve distribúcie nemenných dát. Okrem toho možno túto schému zovšeobecniť aj na iné prípady, kde obsah nie je jednoducho statický, ale môže byť reprezentovaný ako súbor nemenných a nevymazateľných blokov (hoci môžu byť pridané).

Ako ďalší príklad (na posilnenie): ak ste pracovali s Jenkins/TeamCity, potom viete, že obe riešenia sú napísané v jazyku Java. Oba sú procesom Java, ktorý sa zaoberá orchestráciou zostavovania aj správou obsahu. Obaja majú najmä úlohy ako „preniesť súbor/priečinok zo servera“. Napríklad: vydávanie artefaktov, prenos zdrojového kódu (keď agent nesťahuje kód priamo z úložiska, ale server to robí za neho), prístup k protokolom. Všetky tieto úlohy sa líšia svojim IO zaťažením. To znamená, že sa ukazuje, že server zodpovedný za komplexnú obchodnú logiku musí byť zároveň schopný efektívne pretláčať veľké toky údajov cez seba. A čo je najzaujímavejšie je, že takáto operácia môže byť delegovaná na rovnaký nginx podľa presne rovnakej schémy (okrem toho, že dátový kľúč by mal byť pridaný k žiadosti).

Ak sa však vrátime do nášho systému, dostaneme podobný diagram:

Pohodlné architektonické vzory

Ako vidíte, systém sa stal radikálne zložitejším. Teraz to nie je len mini-proces, ktorý ukladá súbory lokálne. Teraz nie je potrebná najjednoduchšia podpora, kontrola verzie API atď. Preto je najlepšie po nakreslení všetkých diagramov podrobne zhodnotiť, či sa rozšíriteľnosť oplatí. Ak však chcete mať možnosť rozširovať systém (vrátane práce s ešte väčším počtom používateľov), potom budete musieť siahnuť po podobných riešeniach. Ale vďaka tomu je systém architektonicky pripravený na zvýšenú záťaž (takmer každý komponent je možné naklonovať na horizontálne škálovanie). Systém je možné aktualizovať bez jeho zastavenia (jednoducho sa niektoré operácie mierne spomalia).

Ako som povedal na úplnom začiatku, teraz sa množstvo internetových služieb začalo viac zaťažovať. A niektoré z nich jednoducho prestali správne fungovať. Systémy totiž zlyhali práve v momente, keď mal biznis zarábať. To znamená, že namiesto odloženého doručenia namiesto toho, aby zákazníkom navrhoval „naplánujte si doručenie na najbližšie mesiace“, systém jednoducho povedal „choďte ku konkurencii“. V skutočnosti je to cena nízkej produktivity: straty nastanú práve vtedy, keď budú zisky najvyššie.

Záver

Všetky tieto prístupy boli známe už predtým. Rovnaký VK už dlho používa myšlienku hostenia statického obsahu na zobrazovanie obrázkov. Mnoho online hier používa schému Sharding na rozdelenie hráčov do regiónov alebo na oddelenie herných miest (ak svet sám je jedným). V e-mailoch sa aktívne využíva prístup Event Sourcing. Väčšina obchodných aplikácií, kde sa údaje neustále prijímajú, je v skutočnosti postavená na prístupe CQRS, aby bolo možné prijaté údaje filtrovať. Horizontálne škálovanie sa používa v mnohých službách už dosť dlho.

Čo je však najdôležitejšie, všetky tieto vzory sa stali veľmi ľahko použiteľnými v moderných aplikáciách (samozrejme, ak sú vhodné). Cloudy ponúkajú Sharding a horizontálne škálovanie hneď, čo je oveľa jednoduchšie, ako si sami objednávať rôzne dedikované servery v rôznych dátových centrách. CQRS sa stalo oveľa jednoduchšie, už len kvôli rozvoju knižníc, ako je RX. Asi pred 10 rokmi to mohla podporovať vzácna webová stránka. Event Sourcing je tiež neuveriteľne jednoduchý na nastavenie vďaka hotovým kontajnerom s Apache Kafka. Pred 10 rokmi by to bola inovácia, teraz je to bežné. Je to rovnaké ako pri hostovaní statického obsahu: vďaka pohodlnejším technológiám (vrátane skutočnosti, že existuje podrobná dokumentácia a veľká databáza odpovedí) sa tento prístup ešte zjednodušil.

Výsledkom je, že implementácia mnohých pomerne zložitých architektonických vzorov je teraz oveľa jednoduchšia, čo znamená, že je lepšie sa na to vopred bližšie pozrieť. Ak sa v desaťročnej aplikácii upustilo od jedného z vyššie uvedených riešení z dôvodu vysokých nákladov na implementáciu a prevádzku, teraz, v novej aplikácii alebo po refaktoringu, môžete vytvoriť službu, ktorá už bude architektonicky rozšíriteľná ( z hľadiska výkonu) a pripravené na nové požiadavky klientov (napríklad na lokalizáciu osobných údajov).

A čo je najdôležitejšie: nepoužívajte tieto prístupy, ak máte jednoduchú aplikáciu. Áno, sú krásne a zaujímavé, ale na stránku s vrcholnou návštevnosťou 100 ľudí si často vystačíte aj s klasickým monolitom (aspoň zvonku sa dá všetko vo vnútri rozdeliť na moduly a pod.).

Zdroj: hab.com

Pridať komentár