Stavebné bloky distribuovaných aplikácií. Druhá aproximácia

Oznámenie

Kolegovia, v polovici leta plánujem vydať ďalšiu sériu článkov o dizajne čakacích systémov: „The VTrade Experiment“ - pokus o napísanie rámca pre obchodné systémy. Séria bude skúmať teóriu a prax budovania burzy, aukcie a obchodu. Na konci článku vás pozývam, aby ste hlasovali za témy, ktoré vás najviac zaujímajú.

Stavebné bloky distribuovaných aplikácií. Druhá aproximácia

Toto je posledný článok zo série o distribuovaných reaktívnych aplikáciách v Erlang/Elixir. IN prvý článok môžete nájsť teoretické základy reaktívnej architektúry. Druhý článok ilustruje základné vzorce a mechanizmy na konštrukciu takýchto systémov.

Dnes nastolíme otázky vývoja kódovej základne a projektov vo všeobecnosti.

Organizácia služieb

V reálnom živote pri vývoji služby musíte často kombinovať niekoľko vzorcov interakcie v jednom ovládači. Napríklad užívateľská služba, ktorá rieši problém správy užívateľských profilov projektu, musí reagovať na požiadavky req-resp a hlásiť aktualizácie profilu cez pub-sub. Tento prípad je celkom jednoduchý: za odosielaním správ je jeden kontrolér, ktorý implementuje logiku služby a zverejňuje aktualizácie.

Situácia sa komplikuje, keď potrebujeme implementovať distribuovanú službu odolnú voči chybám. Predstavme si, že sa zmenili požiadavky na používateľov:

  1. teraz by služba mala spracovávať požiadavky na 5 klastrových uzloch,
  2. byť schopný vykonávať úlohy spracovania na pozadí,
  3. a tiež byť schopný dynamicky spravovať zoznamy odberov pre aktualizácie profilu.

komentár: Neberieme do úvahy otázku konzistentného ukladania a replikácie údajov. Predpokladajme, že tieto problémy boli vyriešené skôr a systém už má spoľahlivú a škálovateľnú úložnú vrstvu a manipulátory majú mechanizmy na interakciu s ňou.

Formálny popis užívateľskej služby sa stal komplikovanejším. Z pohľadu programátora sú zmeny vďaka použitiu správ minimálne. Aby sme splnili prvú požiadavku, musíme nakonfigurovať vyvažovanie v bode výmeny req-resp.

Požiadavka na spracovanie úloh na pozadí sa vyskytuje často. V používateľoch to môže byť kontrola používateľských dokumentov, spracovanie stiahnutých multimédií alebo synchronizácia údajov so sociálnymi médiami. siete. Tieto úlohy je potrebné nejakým spôsobom rozdeliť v rámci klastra a sledovať priebeh vykonávania. Preto máme dve možnosti riešenia: buď použijeme šablónu distribúcie úloh z predchádzajúceho článku, alebo ak nevyhovuje, napíšeme vlastný plánovač úloh, ktorý bude spravovať pool procesorov tak, ako potrebujeme.

Bod 3 vyžaduje rozšírenie šablóny pub-sub. A na implementáciu, po vytvorení miesta výmeny pub-sub, musíme dodatočne spustiť ovládač tohto bodu v rámci našej služby. Logiku spracovania odberov a odhlásení z vrstvy správ teda akoby sme presunuli do implementácie používateľov.

V dôsledku toho rozklad problému ukázal, že na splnenie požiadaviek musíme spustiť 5 inštancií služby na rôznych uzloch a vytvoriť ďalšiu entitu - kontrolór pub-sub, zodpovedný za predplatné.
Ak chcete spustiť 5 obslužných programov, nemusíte meniť servisný kód. Jedinou dodatočnou akciou je nastavenie pravidiel vyvažovania na výmennom mieste, o ktorom si povieme trochu neskôr.
Existuje aj ďalšia zložitosť: ovládač pub-sub a vlastný plánovač úloh musia fungovať v jednej kópii. Opäť platí, že služba zasielania správ ako základná musí poskytovať mechanizmus na výber vedúceho.

Výber vodcu

V distribuovaných systémoch je voľba lídra postupom na vymenovanie jediného procesu zodpovedného za plánovanie distribuovaného spracovania určitej záťaže.

V systémoch, ktoré nie sú náchylné na centralizáciu, sa používajú univerzálne algoritmy a algoritmy založené na konsenze, ako je paxos alebo raft.
Keďže zasielanie správ je sprostredkovateľ a centrálny prvok, vie o všetkých kontrolóroch služieb – kandidátoch na lídrov. Správy môžu vymenovať vedúceho bez hlasovania.

Po spustení a pripojení k výmennému bodu všetky služby dostanú systémovú správu #'$leader'{exchange = ?EXCHANGE, pid = LeaderPid, servers = Servers}. Ak LeaderPid sa zhoduje s pid aktuálneho procesu, je vymenovaný za vedúceho a zoznam Servers zahŕňa všetky uzly a ich parametre.
V momente, keď sa objaví nový a funkčný uzol klastra je odpojený, prijímajú všetky radiče služieb #'$slave_up'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts} и #'$slave_down'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts} resp.

Týmto spôsobom sú si všetky komponenty vedomé všetkých zmien a klaster je zaručene, že bude mať v danom čase jedného lídra.

Sprostredkovatelia

Na implementáciu zložitých procesov distribuovaného spracovania, ako aj pri problémoch s optimalizáciou existujúcej architektúry je vhodné použiť sprostredkovateľov.
Aby ste nemenili kód služby a riešili napríklad problémy s dodatočným spracovaním, smerovaním alebo protokolovaním správ, môžete pred službou povoliť proxy handler, ktorý vykoná všetky dodatočné práce.

Klasickým príkladom optimalizácie pub-sub je distribuovaná aplikácia s obchodným jadrom, ktoré generuje aktualizačné udalosti, ako sú zmeny cien na trhu, a prístupová vrstva - N servery, ktoré poskytujú websocket API pre webových klientov.
Ak sa rozhodnete priamo, potom zákaznícky servis vyzerá takto:

  • klient nadviaže spojenie s platformou. Na strane servera, ktorý ukončí prenos, sa spustí proces na obsluhu tohto spojenia.
  • V kontexte servisného procesu dochádza k autorizácii a prihláseniu na odber aktualizácií. Proces volá metódu odberu pre témy.
  • Akonáhle je udalosť vygenerovaná v jadre, je doručená procesom, ktoré obsluhujú pripojenia.

Predstavme si, že máme 50000 5 odberateľov témy „správy“. Predplatitelia sú rozdelení rovnomerne na 50000 serverov. Výsledkom je, že každá aktualizácia, ktorá príde na miesto výmeny, bude replikovaná 10000 XNUMX-krát: XNUMX XNUMX-krát na každom serveri, podľa počtu predplatiteľov na ňom. Nie veľmi efektívna schéma, však?
Na zlepšenie situácie predstavme proxy, ktorý má rovnaký názov ako výmenný bod. Globálny registrátor názvov musí byť schopný vrátiť najbližší proces podľa mena, čo je dôležité.

Spustite tento proxy server na serveroch prístupovej vrstvy a všetky naše procesy obsluhujúce rozhranie websocket api sa prihlásia na jeho odber a nie na pôvodný výmenný bod pub-sub v jadre. Proxy sa prihlási k jadru iba v prípade jedinečného predplatného a replikuje prichádzajúcu správu všetkým svojim predplatiteľom.
Výsledkom je, že medzi jadrom a prístupovými servermi sa namiesto 5 50000 odošle XNUMX správ.

Smerovanie a vyváženie

Req-Resp

V súčasnej implementácii správ existuje 7 stratégií distribúcie žiadostí:

  • default. Žiadosť sa odošle všetkým kontrolórom.
  • round-robin. Požiadavky sú počítané a cyklicky distribuované medzi kontroléry.
  • consensus. Kontrolóri, ktorí obsluhujú službu, sa delia na vodcov a otrokov. Žiadosti sa posielajú iba vedúcemu.
  • consensus & round-robin. Skupina má vedúceho, ale požiadavky sú rozdelené medzi všetkých členov.
  • sticky. Hašovacia funkcia je vypočítaná a priradená konkrétnemu handleru. Následné požiadavky s týmto podpisom idú rovnakému spracovateľovi.
  • sticky-fun. Pri inicializácii výmenného bodu funkcia výpočtu hash pre sticky vyrovnávanie.
  • fun. Podobne ako pri sticky-fun ho môžete dodatočne presmerovať, odmietnuť alebo predbežne spracovať.

Distribučná stratégia je nastavená pri inicializácii výmenného bodu.

Okrem vyvažovania vám správy umožňujú označovať entity. Pozrime sa na typy značiek v systéme:

  • Značka pripojenia. Umožňuje vám pochopiť, cez aké spojenie udalosti prišli. Používa sa, keď sa proces ovládača pripája k rovnakému výmennému bodu, ale s rôznymi smerovacími kľúčmi.
  • Servisný štítok. Umožňuje vám spojiť obslužné nástroje do skupín pre jednu službu a rozšíriť možnosti smerovania a vyvažovania. Pre vzor req-resp je smerovanie lineárne. Požiadavku odošleme výmennému miestu, tá ju následne odovzdá službe. Ale ak potrebujeme rozdeliť handlery do logických skupín, tak rozdelenie sa robí pomocou tagov. Pri zadávaní tagu bude požiadavka odoslaná konkrétnej skupine kontrolérov.
  • Vyžiadať značku. Umožňuje rozlišovať medzi odpoveďami. Keďže náš systém je asynchrónny, na spracovanie odpovedí služby musíme byť schopní zadať RequestTag pri odosielaní požiadavky. Z nej budeme vedieť pochopiť odpoveď, ktorá požiadavka k nám prišla.

Pub-sub

Pre pub-sub je všetko trochu jednoduchšie. Máme výmenný bod, na ktorý sa zverejňujú správy. Výmenný bod distribuuje správy medzi účastníkov, ktorí si predplatili potrebné smerovacie kľúče (môžeme povedať, že je to analogické s témami).

Škálovateľnosť a odolnosť voči chybám

Škálovateľnosť systému ako celku závisí od stupňa škálovateľnosti vrstiev a komponentov systému:

  • Služby sa škálujú pridaním ďalších uzlov do klastra s obslužnými programami pre túto službu. Počas skúšobnej prevádzky si môžete zvoliť optimálnu politiku vyváženia.
  • Samotná služba zasielania správ v rámci samostatného klastra je vo všeobecnosti škálovaná buď presunutím obzvlášť zaťažených výmenných bodov do samostatných klastrových uzlov, alebo pridaním proxy procesov do obzvlášť zaťažených oblastí klastra.
  • Škálovateľnosť celého systému ako charakteristika závisí od flexibility architektúry a schopnosti spájať jednotlivé klastre do spoločnej logickej entity.

Úspech projektu často závisí od jednoduchosti a rýchlosti škálovania. Messaging vo svojej aktuálnej verzii rastie spolu s aplikáciou. Aj keď nám chýba zhluk 50-60 strojov, môžeme sa uchýliť k federácii. Bohužiaľ, téma federácie je nad rámec tohto článku.

Rezervácia

Pri analýze vyvažovania záťaže sme už diskutovali o redundancii servisných radičov. Posielanie správ však musí byť tiež vyhradené. V prípade havárie uzla alebo stroja by sa správy mali automaticky obnoviť a v čo najkratšom čase.

Vo svojich projektoch používam prídavné uzly, ktoré zdvihnú záťaž v prípade pádu. Erlang má štandardnú implementáciu distribuovaného režimu pre aplikácie OTP. Distribuovaný režim vykonáva obnovu v prípade zlyhania spustením neúspešnej aplikácie na inom predtým spustenom uzle. Proces je transparentný, po zlyhaní sa aplikácia automaticky presunie do núdzového uzla. Môžete si prečítať viac o tejto funkcii tu.

produktivita

Skúsme si aspoň zhruba porovnať výkon rabbitmq a nášho vlastného zasielania správ.
našiel som oficiálne výsledky králičie testovanie od tímu openstack.

V bode 6.14.1.2.1.2.2. Pôvodný dokument zobrazuje výsledok RPC CAST:
Stavebné bloky distribuovaných aplikácií. Druhá aproximácia

Nebudeme vopred vykonávať žiadne ďalšie nastavenia jadra OS alebo virtuálneho počítača erlang. Podmienky na testovanie:

  • erl volí: +A1 +sbtu.
  • Test v rámci jedného uzla erlang prebieha na notebooku so starým i7 v mobilnej verzii.
  • Klastrové testy sa vykonávajú na serveroch so sieťou 10G.
  • Kód beží v dokovacích kontajneroch. Sieť v režime NAT.

Testovací kód:

req_resp_bench(_) ->
  W = perftest:comprehensive(10000,
    fun() ->
      messaging:request(?EXCHANGE, default, ping, self()),
      receive
        #'$msg'{message = pong} -> ok
      after 5000 ->
        throw(timeout)
      end
    end
  ),
  true = lists:any(fun(E) -> E >= 30000 end, W),
  ok.

Scenár 1: Test prebieha na notebooku so starou mobilnou verziou i7. Test, odosielanie správ a služba sa vykonávajú na jednom uzle v jednom kontajneri Docker:

Sequential 10000 cycles in ~0 seconds (26987 cycles/s)
Sequential 20000 cycles in ~1 seconds (26915 cycles/s)
Sequential 100000 cycles in ~4 seconds (26957 cycles/s)
Parallel 2 100000 cycles in ~2 seconds (44240 cycles/s)
Parallel 4 100000 cycles in ~2 seconds (53459 cycles/s)
Parallel 10 100000 cycles in ~2 seconds (52283 cycles/s)
Parallel 100 100000 cycles in ~3 seconds (49317 cycles/s)

Scenár 2: 3 uzly bežiace na rôznych počítačoch pod dockerom (NAT).

Sequential 10000 cycles in ~1 seconds (8684 cycles/s)
Sequential 20000 cycles in ~2 seconds (8424 cycles/s)
Sequential 100000 cycles in ~12 seconds (8655 cycles/s)
Parallel 2 100000 cycles in ~7 seconds (15160 cycles/s)
Parallel 4 100000 cycles in ~5 seconds (19133 cycles/s)
Parallel 10 100000 cycles in ~4 seconds (24399 cycles/s)
Parallel 100 100000 cycles in ~3 seconds (34517 cycles/s)

Vo všetkých prípadoch využitie CPU nepresiahlo 250 %

Výsledky

Dúfam, že tento cyklus nebude vyzerať ako smetisko a moje skúsenosti budú skutočným prínosom pre výskumníkov distribuovaných systémov aj pre odborníkov z praxe, ktorí sú na úplnom začiatku budovania distribuovaných architektúr pre svoje obchodné systémy a so záujmom si prezerajú Erlang/Elixir ale pochybujem, či to stojí za to...

fotografie @chuttersnap

Do prieskumu sa môžu zapojiť iba registrovaní užívatelia. Prihlásiť saProsím.

Akým témam by som sa mal podrobnejšie venovať v rámci série experimentov VTrade?

  • Teória: Trhy, objednávky a ich načasovanie: DAY, GTD, GTC, IOC, FOK, MOO, MOC, LOO, LOC

  • Kniha objednávok. Teória a prax implementácie knihy so zoskupeniami

  • Vizualizácia obchodovania: Ticks, bary, rezolúcie. Ako skladovať a ako lepiť

  • Backoffice. Plánovanie a rozvoj. Monitorovanie zamestnancov a vyšetrovanie incidentov

  • API. Poďme zistiť, aké rozhrania sú potrebné a ako ich implementovať

  • Ukladanie informácií: PostgreSQL, Timescale, Tarantool v obchodných systémoch

  • Reaktivita v obchodných systémoch

  • Iné. Napíšem do komentárov

Hlasovalo 6 užívateľov. 4 používatelia sa zdržali hlasovania.

Zdroj: hab.com

Pridať komentár