Gradniki porazdeljenih aplikacij. Drugi približek

Obvestilo

Kolegi, sredi poletja nameravam izdati še eno serijo člankov o načrtovanju čakalnih sistemov: "VTrade Experiment" - poskus pisanja ogrodja za sisteme trgovanja. Serija bo preučevala teorijo in prakso gradnje borze, dražbe in trgovine. Na koncu članka vas vabim, da glasujete za teme, ki vas najbolj zanimajo.

Gradniki porazdeljenih aplikacij. Drugi približek

To je zadnji članek v seriji o porazdeljenih reaktivnih aplikacijah v Erlang/Elixir. IN prvi članek lahko najdete teoretične temelje reaktivne arhitekture. Drugi člen ponazarja osnovne vzorce in mehanizme za gradnjo takih sistemov.

Danes bomo izpostavili vprašanja razvoja kodne baze in projektov na splošno.

Organizacija storitev

V resničnem življenju morate pri razvoju storitve pogosto združiti več vzorcev interakcije v enem krmilniku. Uporabniška storitev, ki rešuje problem upravljanja projektnih uporabniških profilov, se mora na primer odzvati na zahteve req-resp in poročati o posodobitvah profila prek pub-sub. Ta primer je precej preprost: za sporočili je en krmilnik, ki izvaja logiko storitve in objavlja posodobitve.

Situacija postane bolj zapletena, ko moramo implementirati porazdeljeno storitev, odporno na napake. Predstavljajmo si, da so se zahteve za uporabnike spremenile:

  1. zdaj bi morala storitev obdelati zahteve na 5 vozliščih gruče,
  2. biti sposoben izvajati naloge obdelave v ozadju,
  3. in lahko tudi dinamično upravljate naročniške sezname za posodobitve profila.

Opomba: Ne upoštevamo vprašanja doslednega shranjevanja in podvajanja podatkov. Predpostavimo, da so bile te težave že prej rešene in sistem že ima zanesljivo in razširljivo plast za shranjevanje, upravljavci pa imajo mehanizme za interakcijo z njo.

Formalni opis uporabniške storitve je postal bolj zapleten. S programerskega vidika so spremembe minimalne zaradi uporabe sporočil. Za izpolnitev prve zahteve moramo konfigurirati uravnoteženje na točki izmenjave req-resp.

Zahteva po obdelavi opravil v ozadju se pojavlja pogosto. Pri uporabnikih je to lahko preverjanje uporabniških dokumentov, obdelava prenesene večpredstavnosti ali sinhronizacija podatkov z družbenimi mediji. omrežja. Te naloge je treba nekako porazdeliti znotraj gruče in spremljati potek izvajanja. Zato imamo dve možnosti rešitve: ali uporabimo predlogo za distribucijo nalog iz prejšnjega članka ali, če ne ustreza, napišemo razporejevalnik opravil po meri, ki bo upravljal skupino procesorjev na način, ki ga potrebujemo.

Točka 3 zahteva razširitev predloge pub-sub. In za izvedbo moramo po ustvarjanju izmenjevalne točke pub-sub dodatno zagnati krmilnik te točke znotraj naše storitve. Tako je, kot da premaknemo logiko obdelave naročnin in odjav iz sporočilne plasti v implementacijo uporabnikov.

Posledično je razgradnja problema pokazala, da moramo za izpolnitev zahtev zagnati 5 primerkov storitve na različnih vozliščih in ustvariti dodatno entiteto - pub-sub krmilnik, odgovoren za naročnino.
Če želite zagnati 5 obdelovalcev, vam ni treba spremeniti storitvene kode. Edino dodatno dejanje je nastavitev pravil uravnoteženja na menjalnem mestu, o čemer bomo govorili malo kasneje.
Obstaja tudi dodatna zapletenost: krmilnik pub-sub in razporejevalnik opravil po meri morata delovati v eni sami kopiji. Še enkrat, storitev sporočanja mora kot temeljna zagotavljati mehanizem za izbiro vodje.

Izbira voditelja

V porazdeljenih sistemih je izbira vodje postopek za imenovanje enega samega procesa, ki je odgovoren za načrtovanje porazdeljene obdelave neke obremenitve.

V sistemih, ki niso nagnjeni k centralizaciji, se uporabljajo univerzalni algoritmi, ki temeljijo na soglasju, kot sta paxos ali raft.
Ker je sporočanje posrednik in osrednji element, pozna vse kontrolorje storitev – kandidate za vodje. Messaging lahko imenuje vodjo brez glasovanja.

Po zagonu in povezavi z menjalno točko vse storitve prejmejo sistemsko sporočilo #'$leader'{exchange = ?EXCHANGE, pid = LeaderPid, servers = Servers}. če LeaderPid sovpada z pid tekočem procesu, je imenovan za vodjo in seznam Servers vključuje vsa vozlišča in njihove parametre.
V trenutku, ko se pojavi novo in je delujoče vozlišče gruče prekinjeno, prejmejo vsi krmilniki storitev #'$slave_up'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts} и #'$slave_down'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts} zaporedju.

Tako so vse komponente seznanjene z vsemi spremembami, grozd pa ima v vsakem trenutku zagotovljeno enega vodjo.

posredniki

Za izvajanje kompleksnih procesov porazdeljene obdelave, pa tudi pri problemih optimizacije obstoječe arhitekture, je priročno uporabiti posrednike.
Da ne bi spreminjali servisne kode in reševali na primer težav z dodatno obdelavo, usmerjanjem ali beleženjem sporočil, lahko pred storitvijo omogočite proxy handler, ki bo opravil vse dodatno delo.

Klasičen primer optimizacije pub-sub je porazdeljena aplikacija s poslovnim jedrom, ki generira dogodke posodobitev, kot so spremembe cen na trgu, in plast dostopa – N strežniki, ki zagotavljajo API websocket za spletne odjemalce.
Če se odločite direktno, potem izgleda služba za stranke takole:

  • odjemalec vzpostavi povezave s platformo. Na strani strežnika, ki prekine promet, se sproži proces za servisiranje te povezave.
  • V okviru storitvenega procesa pride do avtorizacije in naročanja na posodobitve. Postopek pokliče metodo naročanja na teme.
  • Ko je dogodek generiran v jedru, je dostavljen procesom, ki servisirajo povezave.

Predstavljajmo si, da imamo 50000 naročnikov na temo »novice«. Naročniki so enakomerno porazdeljeni na 5 strežnikov. Posledično bo vsaka posodobitev, ki prispe na točko izmenjave, podvojena 50000-krat: 10000-krat na vsakem strežniku, glede na število naročnikov na njem. Ni zelo učinkovita shema, kajne?
Da bi izboljšali situacijo, uvedimo proxy, ki ima isto ime kot točka izmenjave. Globalni registrar imen mora imeti možnost vrniti najbližji proces po imenu, to je pomembno.

Zaženimo ta posredniški strežnik na strežnikih sloja dostopa in vsi naši procesi, ki strežejo API websocket, se bodo naročili nanj in ne na izvirno točko izmenjave pub-sub v jedru. Proxy se naroči na jedro samo v primeru edinstvene naročnine in replicira dohodno sporočilo vsem svojim naročnikom.
Posledično bo med jedrom in dostopnimi strežniki poslanih 5 sporočil namesto 50000.

Usmerjanje in uravnoteženje

Req-Resp

V trenutni izvedbi sporočanja je na voljo 7 strategij distribucije zahtev:

  • default. Zahteva se pošlje vsem upravljavcem.
  • round-robin. Zahtevki so oštevilčeni in ciklično razdeljeni med krmilnike.
  • consensus. Kontrolorji, ki služijo storitvi, so razdeljeni na vodje in sužnje. Zahteve se pošiljajo samo vodji.
  • consensus & round-robin. Skupina ima vodjo, vendar so zahteve porazdeljene med vse člane.
  • sticky. Zgoščevalna funkcija se izračuna in dodeli določenemu obdelovalcu. Naslednje zahteve s tem podpisom gredo istemu upravljavcu.
  • sticky-fun. Pri inicializaciji izmenjevalne točke se funkcija izračuna zgoščevanja za sticky uravnoteženje.
  • fun. Podobno kot sticky-fun, le da ga lahko dodatno preusmerite, zavrnete ali predhodno obdelate.

Strategija distribucije je nastavljena, ko je izmenjalna točka inicializirana.

Poleg uravnoteženja vam sporočanje omogoča označevanje entitet. Oglejmo si vrste oznak v sistemu:

  • Oznaka povezave. Omogoča vam razumevanje, skozi katero povezavo so dogodki prišli. Uporablja se, ko se proces krmilnika poveže z isto točko izmenjave, vendar z različnimi usmerjevalnimi ključi.
  • Servisna oznaka. Omogoča združevanje upravljavcev v skupine za eno storitev in razširitev zmogljivosti usmerjanja in uravnoteženja. Za vzorec req-resp je usmerjanje linearno. Zahtevek pošljemo menjalnici, ta pa jo posreduje servisu. Če pa moramo upravljalnike razdeliti v logične skupine, se razdelitev izvede z uporabo oznak. Pri določanju oznake bo zahteva poslana določeni skupini krmilnikov.
  • Oznaka zahteve. Omogoča razlikovanje med odgovori. Ker je naš sistem asinhron, moramo za obdelavo odgovorov storitve imeti možnost določiti RequestTag pri pošiljanju zahteve. Iz njega bomo lahko razbrali odgovor, katera zahteva je prišla do nas.

Pub-sub

Za pub-sub je vse nekoliko preprostejše. Imamo izmenjevalno točko, na kateri se objavljajo sporočila. Izmenjalna točka distribuira sporočila med naročnike, ki so se naročili na usmerjevalne ključe, ki jih potrebujejo (lahko rečemo, da je to analogno temam).

Razširljivost in toleranca napak

Razširljivost sistema kot celote je odvisna od stopnje razširljivosti slojev in komponent sistema:

  • Storitve se spreminjajo z dodajanjem dodatnih vozlišč v gručo z obdelovalci za to storitev. Med poskusnim delovanjem lahko izberete optimalno politiko uravnoteženja.
  • Sama storitev sporočanja znotraj ločene gruče se na splošno poveča s premikanjem posebno obremenjenih točk izmenjave v ločena vozlišča gruče ali z dodajanjem proxy procesov posebej obremenjenim področjem gruče.
  • Razširljivost celotnega sistema je kot lastnost odvisna od fleksibilnosti arhitekture in zmožnosti združevanja posameznih grozdov v skupno logično entiteto.

Uspeh projekta je pogosto odvisen od enostavnosti in hitrosti skaliranja. Sporočila v trenutni različici rastejo skupaj z aplikacijo. Tudi če nam manjka grozd 50-60 strojev, se lahko zatečemo k federaciji. Na žalost je tema federacije izven obsega tega članka.

Rezervacija

Pri analizi uravnoteženja obremenitve smo že razpravljali o redundanci servisnih krmilnikov. Vendar je treba tudi sporočanje rezervirati. V primeru zrušitve vozlišča ali stroja se mora sporočanje samodejno obnoviti in v najkrajšem možnem času.

V svojih projektih uporabljam dodatne vozle, ki prevzamejo obremenitev v primeru padca. Erlang ima standardno implementacijo porazdeljenega načina za aplikacije OTP. Porazdeljeni način izvede obnovitev v primeru okvare z zagonom neuspele aplikacije na drugem predhodno zagnanem vozlišču. Postopek je pregleden, po napaki se aplikacija samodejno premakne v failover vozlišče. Več o tej funkciji lahko preberete tukaj.

Produktivnost

Poskusimo vsaj približno primerjati delovanje rabbitmq in našega sporočila po meri.
našel sem uradni rezultati testiranje rabbitmq iz ekipe openstack.

V odstavku 6.14.1.2.1.2.2. Izvirni dokument prikazuje rezultat RPC CAST:
Gradniki porazdeljenih aplikacij. Drugi približek

Vnaprej ne bomo izvajali nobenih dodatnih nastavitev jedra OS ali erlang VM. Pogoji za testiranje:

  • erl se odloči: +A1 +sbtu.
  • Preizkus v enem vozlišču erlang se izvaja na prenosnem računalniku s starim i7 v mobilni različici.
  • Testi grozdov se izvajajo na strežnikih z omrežjem 10G.
  • Koda se izvaja v docker vsebnikih. Omrežje v načinu NAT.

Testna koda:

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.

Scenarij 1: Test se izvede na prenosniku s staro mobilno različico i7. Test, sporočanje in storitev se izvajajo na enem vozlišču v enem vsebniku 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)

Scenarij 2: 3 vozlišča, ki se izvajajo na različnih strojih pod dockerjem (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)

V vseh primerih izkoriščenost procesorja ni presegla 250 %

Rezultati

Upam, da ta cikel ne bo videti kot smetišče in da bodo moje izkušnje resnično koristile tako raziskovalcem porazdeljenih sistemov kot strokovnjakom, ki so na samem začetku gradnje porazdeljenih arhitektur za svoje poslovne sisteme in z zanimanjem gledajo na Erlang/Elixir , vendar dvomite, ali je vredno ...

Photo Shoot @chuttersnap

V anketi lahko sodelujejo samo registrirani uporabniki. Prijaviti se, prosim.

Katere teme naj podrobneje obravnavam kot del serije VTrade Experiment?

  • Teorija: Trgi, naročila in njihov čas: DAY, GTD, GTC, IOC, FOK, MOO, MOC, LOO, LOC

  • Knjiga naročil. Teorija in praksa izvajanja knjige z združevanjem

  • Vizualizacija trgovanja: kljukice, stolpci, resolucije. Kako shraniti in kako lepiti

  • Zaledna pisarna. Načrtovanje in razvoj. Spremljanje zaposlenih in preiskava incidentov

  • API. Ugotovimo, kateri vmesniki so potrebni in kako jih implementirati

  • Shranjevanje informacij: PostgreSQL, Timescale, Tarantool v trgovalnih sistemih

  • Reaktivnost trgovalnih sistemov

  • drugo. Napisal bom v komentarjih

Glasovalo je 6 uporabnikov. 4 uporabnika sta se vzdržala.

Vir: www.habr.com

Dodaj komentar