Paskirstytų programų blokai. Antrasis aproksimavimas

Skelbimas

Kolegos, vasaros viduryje planuoju išleisti dar vieną straipsnių seriją apie eilių sistemų dizainą: „The VTrade Experiment“ – bandymas parašyti prekybos sistemų pagrindą. Serija nagrinės biržos, aukciono ir parduotuvės kūrimo teoriją ir praktiką. Straipsnio pabaigoje kviečiu balsuoti už labiausiai jus dominančias temas.

Paskirstytų programų blokai. Antrasis aproksimavimas

Tai paskutinis straipsnis apie paskirstytas reaktyviąsias programas Erlang / Elixir. IN pirmasis straipsnis galite rasti reaktyviosios architektūros teorinius pagrindus. Antras straipsnis iliustruoja pagrindinius tokių sistemų kūrimo modelius ir mechanizmus.

Šiandien kelsime kodų bazės kūrimo ir projektų apskritai klausimus.

Paslaugų organizavimas

Realiame gyvenime kuriant paslaugą dažnai tenka sujungti kelis sąveikos modelius viename valdiklyje. Pavyzdžiui, vartotojų paslauga, kuri išsprendžia projekto vartotojų profilių valdymo problemą, turi atsakyti į req-resp užklausas ir pranešti apie profilio atnaujinimus per pub-sub. Šis atvejis gana paprastas: už pranešimų siuntimo yra vienas valdiklis, kuris įgyvendina paslaugų logiką ir skelbia atnaujinimus.

Situacija tampa sudėtingesnė, kai reikia įdiegti gedimams atsparią paskirstytą paslaugą. Įsivaizduokime, kad pasikeitė reikalavimai vartotojams:

  1. dabar paslauga turėtų apdoroti užklausas 5 klasterio mazguose,
  2. mokėti atlikti foninio apdorojimo užduotis,
  3. taip pat galėsite dinamiškai tvarkyti profilio naujinimų prenumeratos sąrašus.

Pastaba: Mes nesvarstome nuoseklaus duomenų saugojimo ir atkartojimo klausimo. Tarkime, kad šios problemos buvo išspręstos anksčiau ir sistemoje jau yra patikimas ir keičiamo dydžio saugojimo sluoksnis, o tvarkytojai turi mechanizmus, kaip su juo sąveikauti.

Formalus vartotojų paslaugos aprašymas tapo sudėtingesnis. Programuotojo požiūriu, dėl pranešimų siuntimo pakeitimai yra minimalūs. Norėdami patenkinti pirmąjį reikalavimą, turime sukonfigūruoti balansavimą req-resp mainų taške.

Reikalavimas atlikti fonines užduotis atsiranda dažnai. Vartotojams tai gali būti vartotojo dokumentų tikrinimas, atsisiųstos daugialypės terpės apdorojimas arba duomenų sinchronizavimas su socialine žiniasklaida. tinklus. Šios užduotys turi būti kažkaip paskirstytos klasteryje ir stebėti vykdymo eigą. Todėl turime dvi sprendimo parinktis: arba naudoti užduočių paskirstymo šabloną iš ankstesnio straipsnio, arba, jei jis netinka, parašyti pasirinktinį užduočių planuoklį, kuris tvarkys procesorių telkinį taip, kaip mums reikia.

3 punktas reikalauja pub-sub šablono plėtinio. O įgyvendinimui, sukūrę pub-sub mainų tašką, turime papildomai paleisti šio taško valdiklį savo paslaugoje. Taigi prenumeratos ir prenumeratos atsisakymo apdorojimo logiką tarsi perkeliame iš pranešimų sluoksnio į vartotojų diegimą.

Dėl to problemos išskaidymas parodė, kad norint atitikti reikalavimus, turime paleisti 5 paslaugos egzempliorius skirtinguose mazguose ir sukurti papildomą subjektą - pub-sub valdiklį, atsakingą už prenumeratą.
Norint paleisti 5 tvarkykles, nereikia keisti paslaugos kodo. Vienintelis papildomas veiksmas yra balansavimo taisyklių nustatymas mainų punkte, apie kurį pakalbėsime šiek tiek vėliau.
Taip pat yra papildomas sudėtingumas: publikavimo antrinis valdiklis ir pasirinktinis užduočių planuoklis turi veikti vienoje kopijoje. Vėlgi, pranešimų siuntimo paslauga, kaip pagrindinė paslauga, turi numatyti lyderio pasirinkimo mechanizmą.

Lyderio pasirinkimas

Paskirstytose sistemose lyderio rinkimai yra vieno proceso, atsakingo už paskirstyto tam tikros apkrovos apdorojimo planavimą, paskyrimo procedūra.

Sistemose, kurios nėra linkusios centralizuoti, naudojami universalūs ir konsensusu pagrįsti algoritmai, tokie kaip paxos arba raftas.
Kadangi pranešimų siuntimas yra tarpininkas ir centrinis elementas, jis žino apie visus paslaugų kontrolierius – kandidatus į lyderius. Pranešimų siuntimas gali paskirti lyderį be balsavimo.

Paleidus ir prisijungus prie mainų taško visos paslaugos gauna sistemos pranešimą #'$leader'{exchange = ?EXCHANGE, pid = LeaderPid, servers = Servers}. Jeigu LeaderPid sutampa su pid einamąjį procesą, jis paskiriamas lyderiu ir sąraše Servers apima visus mazgus ir jų parametrus.
Šiuo metu atsiranda naujas ir atjungiamas veikiantis klasterio mazgas, gauna visi paslaugų valdikliai #'$slave_up'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts} и #'$slave_down'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts} atitinkamai.

Tokiu būdu visi komponentai žino apie visus pokyčius ir garantuojama, kad klasteris bet kuriuo metu turės vieną lyderį.

Tarpininkai

Norint įgyvendinti sudėtingus paskirstytus apdorojimo procesus, taip pat esant esamos architektūros optimizavimo problemoms, patogu naudoti tarpininkus.
Kad nekeistumėte paslaugos kodo ir išspręstumėte, pavyzdžiui, papildomo pranešimų apdorojimo, maršruto parinkimo ar registravimo problemas, prieš paslaugą galite įjungti proxy tvarkyklę, kuri atliks visus papildomus darbus.

Klasikinis pub-sub optimizavimo pavyzdys yra paskirstyta programa su verslo branduoliu, generuojančiu atnaujinimo įvykius, pvz., kainų pokyčius rinkoje, ir prieigos lygmeniu – N serverių, kurie teikia žiniatinklio prievado API žiniatinklio klientams.
Jei nuspręsite tiesiai, klientų aptarnavimas atrodys taip:

  • klientas užmezga ryšius su platforma. Toje serverio pusėje, kuri nutraukia srautą, paleidžiamas šio ryšio aptarnavimo procesas.
  • Paslaugų teikimo proceso metu atsiranda autorizacija ir naujinimų prenumerata. Procesas iškviečia temų prenumeratos metodą.
  • Kai branduolyje sugeneruojamas įvykis, jis perduodamas ryšius aptarnaujantiems procesams.

Įsivaizduokime, kad turime 50000 5 „naujienų“ temos prenumeratorių. Abonentai yra tolygiai paskirstyti 50000 serveriuose. Dėl to kiekvienas atnaujinimas, atvykęs į mainų tašką, bus pakartotas 10000 XNUMX kartų: XNUMX XNUMX kartų kiekviename serveryje, atsižvelgiant į jame esančių abonentų skaičių. Nelabai efektyvi schema, tiesa?
Norėdami pagerinti situaciją, pristatykime tarpinį serverį, kurio pavadinimas yra toks pat kaip ir mainų taško. Pasaulinis vardų registratorius turi turėti galimybę grąžinti artimiausią procesą pagal pavadinimą, tai svarbu.

Paleiskime šį tarpinį serverį prieigos lygmens serveriuose ir visi mūsų procesai, aptarnaujantys žiniatinklio lizdo API, užsiprenumeruos jį, o ne pradinį pub-sub mainų tašką branduolyje. Įgaliotasis serveris prenumeruoja branduolį tik unikalios prenumeratos atveju ir atkartoja gaunamą pranešimą visiems savo abonentams.
Dėl to tarp branduolio ir prieigos serverių bus išsiųsti 5 pranešimai, o ne 50000 XNUMX.

Maršrutas ir balansavimas

Req-Resp

Dabartiniame pranešimų siuntimo diegime yra 7 užklausų paskirstymo strategijos:

  • default. Prašymas siunčiamas visiems kontrolieriams.
  • round-robin. Užklausos yra surašytos ir cikliškai paskirstomos tarp valdiklių.
  • consensus. Tarnybą aptarnaujantys kontrolieriai skirstomi į lyderius ir vergus. Prašymai siunčiami tik vadovui.
  • consensus & round-robin. Grupė turi lyderį, tačiau prašymai paskirstomi visiems nariams.
  • sticky. Maišos funkcija apskaičiuojama ir priskiriama konkrečiam tvarkytojui. Vėlesnės užklausos su šiuo parašu siunčiamos tam pačiam tvarkytojui.
  • sticky-fun. Inicijuojant mainų tašką, maišos skaičiavimo funkcija sticky balansavimas.
  • fun. Panašiai kaip lipnus linksmumas, tik jūs galite jį papildomai peradresuoti, atmesti arba iš anksto apdoroti.

Paskirstymo strategija nustatoma inicijuojant mainų tašką.

Be balansavimo, pranešimų siuntimas leidžia pažymėti objektus. Pažvelkime į sistemos žymų tipus:

  • Ryšio žyma. Leidžia suprasti, per kokį ryšį atėjo įvykiai. Naudojamas, kai valdiklio procesas prisijungia prie to paties mainų taško, bet naudojant skirtingus maršruto raktus.
  • Serviso etiketė. Leidžia sujungti tvarkykles į grupes vienai paslaugai ir išplėsti maršruto parinkimo ir balansavimo galimybes. Req-resp modeliui maršrutas yra linijinis. Išsiunčiame užklausą į mainų punktą, tada jis perduoda jį tarnybai. Bet jei mums reikia padalyti tvarkykles į logines grupes, tada skaidymas atliekamas naudojant žymes. Nurodant žymą, užklausa bus siunčiama konkrečiai valdiklių grupei.
  • Prašyti žymos. Leidžia atskirti atsakymus. Kadangi mūsų sistema yra asinchroninė, norėdami apdoroti paslaugų atsakymus, siųsdami užklausą turime turėti galimybę nurodyti RequestTag. Iš jo galėsime suprasti atsakymą, į kokį prašymą atėjome.

Pub-sub

Pub-sub viskas yra šiek tiek paprasčiau. Turime apsikeitimo tašką, į kurį skelbiami pranešimai. Keitimosi punktas paskirsto pranešimus tarp abonentų, kurie užsisakė jiems reikalingus maršruto parinkimo raktus (galime sakyti, kad tai analogiška temoms).

Mastelio keitimas ir atsparumas gedimams

Visos sistemos mastelio keitimas priklauso nuo sistemos sluoksnių ir komponentų mastelio laipsnio:

  • Paslaugos padidinamos pridedant papildomų mazgų prie klasterio su šios paslaugos tvarkytojais. Bandomojo veikimo metu galite pasirinkti optimalią balansavimo politiką.
  • Pati pranešimų siuntimo paslauga atskirame klasteryje paprastai keičiama perkeliant ypač apkrautus mainų taškus į atskirus klasterio mazgus arba pridedant tarpinio serverio procesus į ypač apkrautas klasterio sritis.
  • Visos sistemos, kaip charakteristikos, mastelio keitimas priklauso nuo architektūros lankstumo ir galimybės sujungti atskiras grupes į bendrą loginį vienetą.

Projekto sėkmė dažnai priklauso nuo mastelio keitimo paprastumo ir greičio. Dabartinės versijos pranešimų siuntimas auga kartu su programa. Net jei mums trūksta 50–60 mašinų klasterio, galime griebtis federacijos. Deja, federacijos tema yra už šio straipsnio ribų.

Rezervacija

Analizuodami apkrovos balansavimą, jau aptarėme serviso valdiklių atleidimą. Tačiau pranešimai taip pat turi būti rezervuoti. Įvykus mazgui ar mašinos gedimui, pranešimų siuntimas turėtų būti automatiškai atkurtas ir per trumpiausią įmanomą laiką.

Savo projektuose naudoju papildomus mazgus, kurie paima apkrovą kritimo atveju. Erlang turi standartinį paskirstyto režimo įgyvendinimą OTP programoms. Paskirstytas režimas gedimo atveju atlieka atkūrimą, paleidžiant nepavykusią programą kitame anksčiau paleistame mazge. Procesas yra skaidrus; po gedimo programa automatiškai persikelia į perkėlimo mazgą. Daugiau apie šią funkciją galite perskaityti čia.

Našumas

Pabandykime bent apytiksliai palyginti rabbitmq ir mūsų tinkintų pranešimų našumą.
aš radau oficialūs rezultatai rabbitmq testavimas iš openstack komandos.

6.14.1.2.1.2.2 punkte. Originaliame dokumente rodomas RPC CAST rezultatas:
Paskirstytų programų blokai. Antrasis aproksimavimas

Iš anksto neatliksime jokių papildomų OS branduolio ar erlang VM nustatymų. Bandymo sąlygos:

  • erl pasirenka: +A1 +sbtu.
  • Bandymas viename erlang mazge vykdomas nešiojamajame kompiuteryje su sena i7 mobiliąja versija.
  • Klasteriniai testai atliekami serveriuose su 10G tinklu.
  • Kodas veikia dokerių konteineriuose. Tinklas veikia NAT režimu.

Bandymo kodas:

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.

1 scenarijus: Bandymas vykdomas nešiojamajame kompiuteryje su sena i7 mobiliąja versija. Bandymas, pranešimų siuntimas ir paslauga vykdomi viename mazge viename „Docker“ konteineryje:

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)

2 scenarijus: 3 mazgai, veikiantys skirtingose ​​mašinose pagal dokerį (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)

Visais atvejais procesoriaus panaudojimas neviršijo 250 proc.

rezultatai

Tikiuosi, kad šis ciklas neatrodo kaip proto sąvartynas ir mano patirtis bus tikrai naudinga tiek paskirstytų sistemų tyrinėtojams, tiek praktikams, kurie tik pradeda kurti paskirstytas architektūras savo verslo sistemoms ir su susidomėjimu žiūri į Erlang/Elixir. , bet abejoji ar verta...

nuotrauka @chuttersnap

Apklausoje gali dalyvauti tik registruoti vartotojai. Prisijungti, Prašau.

Kokias temas turėčiau išsamiau aptarti kaip VTrade Experiment serijos dalį?

  • Teorija: Rinkos, užsakymai ir jų laikas: DAY, GTD, GTC, IOC, FOK, MOO, MOC, LOO, LOC

  • Užsakymų knyga. Knygos su grupavimu įgyvendinimo teorija ir praktika

  • Prekybos vizualizacija: Varnelės, barai, rezoliucijos. Kaip laikyti ir kaip klijuoti

  • Backoffice. Planavimas ir plėtra. Darbuotojų stebėjimas ir incidentų tyrimas

  • API. Išsiaiškinkime, kokių sąsajų reikia ir kaip jas įdiegti

  • Informacijos saugojimas: PostgreSQL, Timescale, Tarantool prekybos sistemose

  • Reaktyvumas prekybos sistemose

  • Kita. Komentaruose parašysiu

Balsavo 6 vartotojų. 4 vartotojai susilaikė.

Šaltinis: www.habr.com

Добавить комментарий