Njoftim
Të dashur kolegë, në mesin e verës planifikoj të publikoj një cikël tjetër artikujsh mbi projektimin e sistemeve të shërbimit masiv: “Eksperiment VTrade” — një përpjekje për të shkruar një kornizë për sistemet tregtare. Në këtë cikël do të shqyrtohet teoria dhe praktika e ndërtimit të një tregu, ankandi dhe dyqani. Në fund të artikullit, ju ftoj të votoni për temat më të interesuara për ju.

Ky është artikulli përfundimtar i ciklit për aplikacione reaktive të shpërndara në Erlang/Elixir. Në mund të gjeni bazat teorike të arkitekturës reaktive. ilustron modelet dhe mekanizmat kryesorë të ndërtimit të sistemeve të tilla.
Sot do të diskutojmë për zhvillimin e kodit bazë dhe projekteve në përgjithësi.
Organizimi i shërbimeve
Në jetën reale, gjatë zhvillimit të një shërbimi, shpesh duhet të bashkohen disa modele ndërveprimi në një kontrollues. Për shembull, shërbimi users, i cili zgjidh problemet e menaxhimit të profileve të përdoruesve të projektit, duhet të përgjigjet në kërkesat req-resp dhe të raportojë për përditësimet e profileve përmes pub-sub. Ky rast është mjaft i thjeshtë: për messaging qëndron një kontrollues, i cili implementon logjikën e shërbimit dhe publikon përditësime.
Situata komplikohet kur na nevojitet të implementojmë një shërbim të shpërndarë rezistent ndaj dështimeve. Le të imagjinojmë se kërkesat për users janë ndryshuar:
- tani shërbimi duhet të përpunojë kërkesat në 5 nyje të klasterit,
- të ketë mundësinë e ekzekutimit të detyrave të prapame,
- si dhe të dinë të menaxhojnë dinamikisht listat e abonimit për përditësimet e profileve.
Shënim: Ne nuk e shqyrtojmë çështjen e ruajtjes konsistente dhe replikimit të të dhënave. Supozojmë se këto çështje janë zgjidhur më parë dhe në sistem tashmë ekziston një shtresë e besueshme dhe e shkallëzueshme e ruajtjes, dhe përpunuesit kanë mekanizma ndërveprimi me të.
Përshkrimi formal i shërbimit users është komplikuar. Nga këndvështrimi i programuesit, falë përdorimit të messaging-ut, ndryshimet janë minime. Për të përmbushur kërkesën e parë, na nevojitet të konfigurojmë balancimin në pikën e ndarjes req-resp.
Kërkesa për përcaktimin e detyrave në sfond ndodh shpesh. Në përdoruesit kjo mund të jetë verifikimi i dokumenteve të përdoruesve, përpunimi i multimediave të ngarkuara, ose sinkronizimi i të dhënave me rrjetet sociale. Këto detyra duhet ndarë ndonjëfarë mënyre brenda klasterit dhe të kontrollohet progresi i ekzekutimit. Prandaj kemi dy mundësi zgjidhjeje: ose të përdorim modelin e shpërndarjes së detyrave nga Artikulli i kaluar, ose, nëse ai nuk është i përshtatshëm, të shkruajmë një planifikues të personalizuar të detyrave që do të menaxhojë pool-in e përpunuesve ashtu siç na nevojitet.
Pika 3 kërkon zgjerimin e modelit pub-sub. Dhe për ta realizuar, pas krijimit të pikës së këmbimit pub-sub, na nevojitet të aktivizojmë një kontrollues të kësaj pike brenda shërbimit tonë. Kështu, duket sikur e heqim logjikën e përpunimit të abonimeve dhe shkëputjeve nga sipari messaging në implementimin e përdoruesve.
Si rezultat, dekompozimi i detyrës tregoi se për të përmbushur kërkesat, na nevojitet të aktivizojmë në nyje të ndryshme 5 kopje të shërbimit dhe të krijojmë një entitet shtesë – kontrolluesin pub-sub, përgjegjës për abonimin.
Për të aktivizuar 5 përpunuesit, nuk kërkohet të ndryshojmë kodin e shërbimit. Veprimi i vetëm shtesë është konfigurimi i rregullave të balancimit në pikën e këmbimit, për të cilin do të flasim pak më vonë.
Gjithashtu ka një kompleksitet shtesë: kontrolluesi pub-sub dhe planifikuesi i personalizuar i detyrave duhet të punojnë në një kopje të vetme. Po ashtu, shërbimi messaging, si një themelor, duhet të ofrojë një mekanizëm për zgjedhjen e liderit.
Zgjedhja e liderit
Në sistemet e shpërndara, zgjedhja e liderit është një procedurë për emërimin e një procesi të vetëm, përgjegjës për planifikimin e përpunimit të shpërndarë të ndonjë ngarkese.
Në sistemet që nuk e preferojnë centralizimin, përdoren algoritme universale dhe algoritme të bazuara në konsensus, siç janë paxos ose raft.
Dhe meqë messaging është një broker dhe element qendror, ai di për të gjithë kontrolluesit e shërbimit – kandidatët për liderë. Messaging mund të emërojë një lider pa organizuar votim.
Të gjitha shërbimet pas fillimit dhe lidhjes me pikën e këmbimit marrin një mesazh sistemik #'$leader'{exchange = ?EXCHANGE, pid = LeaderPid, servers = Servers}. Në rast se LeaderPid përputhet me pid e procesit aktual, ai emërohet lider, dhe lista Servers përfshin të gjitha nyjet dhe parametrat e tyre.
Në momentin e shfaqjes së një nyjeje të re dhe çaktivizimit të një nyjeje funksionale të klasterit, të gjitha kontrolluesit e shërbimit marrin #'$slave_up'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts} и #'$slave_down'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts} përkatësisht.
Kështu, të gjitha komponentët e dinë për të gjitha ndryshimet, dhe në klaster në çdo moment është garantuar një lider.
Mënyra të ndërmjetme
Për realizimin e proceseve të ndarë të përpunimit të informacionit, si dhe në detyrat e optimizimit të arkitekturës ekzistuese, është e lehtë të aplikohen ndërmjetës.
Për të mos ndryshuar kodin e shërbimeve dhe për të zgjidhur, për shembull, problemet e përpunimit shtesë, ruterizimit ose regjistrimit të mesazheve, para shërbimit mund të aktivizohet një përpunues proxy, i cili do të bëjë të gjithë punën shtesë.
Një shembull klasik i optimizimit pub-sub është një aplikacion i shpërndarë me bërthamën e biznesit, që gjeneron ngjarje të përditësimeve, për shembull ndryshimin e çmimit në treg, dhe shtresa e aksesit — N serverë që ofrojnë API websocket për klientët web.
Nëse e zgjidhim ‘ashtu siç është’, shërbimi për klientin duket kështu:
- klienti krijon lidhje me platformën. Në anën e serverit, që terminon trafikun, fillon një proces që shërben këtë lidhje.
- në kontekstin e procesit shërbyes ndodhin autorizimi dhe regjistrimi për përditësime. Procesi thërret metodën subscribe për temat.
- pasi gjenerohet një ngjarje në bërthama, ajo dërgohet në proceset që shërbejnë lidhjet.
Le të supozojmë se kemi 50000 abone në temën “news”. Abonuesit janë shpërndarë në 5 servera në mënyrë të barabartë. Si rezultat, çdo përditësim, duke ardhur në pikën e shkëmbimit, do të riprodhohet 50000 herë: 10000 herë në secilin server, sipas numrit të abonuesve në të. Nuk është një skemë shumë efikase, apo jo?
Për të përmirësuar situatën, le të futim një proxy, që ka të njëjtin emër me pikën e shkëmbimit. Regjistruesi i emrave global duhet të dijë të kthejë procesin më të afërt sipas emrit, kjo është e rëndësishme.
Të nisim këtë proxy në serverat e shtresës së aksesit, dhe të gjithë proceset tona që shërbejnë API websocket do të regjistrohen tek ai, dhe jo në pikën origjinale pub-sub të shkëmbimit në bërthama. Proxy regjistrohet në bërthame vetëm në rastin e regjistrimit unik dhe riprodhon mesazhin e pranuar për të gjithë abonuesit e tij.
Si rezultat, midis bërthamës dhe serverave të aksesit do të dërgohen 5 mesazhe, në vend të 50000.
Ruterizimi dhe balancimi
Req-Resp
Në realizimin aktual të mesazgjerimit ekzistojnë 7 strategji të shpërndarjes së kërkesave:
default. Kërkesa dërgohet të gjithë kontrollorëve.round-robin. Përdoret një cikël dhe shpërndarja e kërkesave bëhet në mënyrë ciklike midis kontrollorëve.konsens. Kontrollorët që shërbejnë shërbimin ndahen në lider dhe ndihmës. Kërkesat dërgohen vetëm te lideri.konsens & rreth-rrotull. Në grup ka një lider, por kërkesat shpërndahen midis të gjithë anëtarëve.ngjitur. Llogaritet funksioni hash dhe lidhet me një trajtues të caktuar. Kërkesat që vijnë me këtë nënshkrim kalojnë te ky trajtues.ngjitur-fun. Gjatë inicializimit të pikës së shkëmbimit, jepet gjithashtu një funksion për llogaritjen e hashes përngjitur. balancimin.fun. I ngjashëm me ngjitur-fun, por gjithashtu mund të përshkruajë, refuzojë ose përpunojë paraprakisht atë.
Strategjia e shpërndarjes përcaktohet gjatë inicializimit të pikës së shkëmbimit.
Përveç balancimit, messaging lejon të etiketoni entitetet. Le të shqyrtojmë llojet e etiketimeve në sistem:
- Etiketa e lidhjes. Lejon të kuptohet se përmes cilës lidhjeje erdhën ngjarjet. Përdoret kur procesi i kontrollorëve lidhet me një pikë shkëmbimi, por me çelësa të ndryshëm të rrugëve.
- Etiketa e shërbimit. Lejon që për një shërbim të bashkojë në grupe trajtuesit dhe të zgjasë mundësitë e rrugës dhe balancimit. Për modelin req-resp, rrugëtimi është linear. Ne dërgojmë një kërkesë te pika e shkëmbimit, më pas ajo e kalon te shërbimi. Por nëse duam t'i ndajmë trajtuesit në grupe logjike, ndarja bëhet me ndihmën e etiketimeve. Me caktimin e etiketes, kërkesa do të drejtohet te një grup specifik kontrollorësh.
- Etiketa e kërkesës. Lejon të dalloni përgjigjet. Duke qenë se sistemi ynë është asinkron, për përpunimin e përgjigjeve të shërbimit duhet të kemi mundësinë të caktuar RequestTag gjatë dërgimit të kërkesës. Përmes saj do të mund të kuptojmë se e cila përgjigje për kërkesën na ka ardhur.
Pub-sub
Për pub-sub, gjërat janë pak më të thjeshta. Ne kemi një pikë shkëmbimi ku publikohen mesazhet. Pika e shkëmbimit shpërndan mesazhet midis abonentëve, të cilët janë abonuar në çelësat e nevojshëm të rrugëtimit (mund ta quajmë se është analog i temave).
Shkallëzimi dhe qëndrueshmëria
Shkallëzimi i sistemit në tërësi varet nga shkalla e shkallëzimit të shtresave dhe komponentëve të sistemit:
- Shërbimet shkallëzohen duke shtuar në kluster nyje shtesë me menaxherë të këtij shërbimi. Gjatë operimeve praktike, mund të zgjidhni politikën optimale të balancimit.
- Shërbimi messaging brenda një klusteri të veçantë zakonisht shkallëzohet ose duke shpërndarë pikat e shkëmbimit të ngarkesës së lartë në nyje të veçanta të klusterit, ose duke shtuar procese proxy në zonat e ngarkesës së lartë të klusterit.
- Shkallëzimi i tërë sistemit si një karakteristikë varet nga fleksibiliteti i arkitekturës dhe mundësia për të bashkuar kluster të veçantë në një entitet logjik të përgjithshëm.
Thjeshtësia dhe shpejtësia e shkallëzimit shpesh përcaktojnë suksesin e projektit. Messaging në aktualitet rritet së bashku me aplikacionin. Edhe nëse na mungojnë klusteret me 50-60 makina, mund të përdorim federimin. Fatkeqësisht, tema e federimit del jashtë kuadrit të këtij artikulli.
Rezervimi
Kur diskutuam për balancimin e ngarkesës, ne tashmë diskutuam për rezervimin e kontrollorëve të shërbimeve. Megjithatë, messaging gjithashtu duhet të jetë i rezervuar. Në rast të rënies së një nyjeje ose makina, messaging duhet të rikuperohet automatikisht, dhe për më shumë, në kohën më të shkurtër të mundshme.
Në projektet e mia përdor nyje shtesë, të cilat kapin ngarkesën në rast rënjeje. Në Erlang ekziston një implementim standard i modit të shpërndarë për aplikacionet OTP. Mod i shpërndarë pikërisht realizon rikuperimin në rast dështimi duke nisur aplikacionin e rënë në një nyje tjetër të nisur më parë. Procesi është transparent, pasi aplikacioni kalon automatikisht te nyja e rezervuar. Për të lexuar më shumë rreth këtij funksionaliteti mund të .
Performanca
Të përpiqemi të krahasojmë me të paktën një afërsisht performancën e rabbitmq dhe messaging tonë të personalizuar.
E kam gjetur e testimit të rabbitmq nga ekipi openstack.
Në pikën 6.14.1.2.1.2.2. të dokumentit origjinal paraqitet rezultati RPC CAST:

Përpara, nuk do të bëjmë asnjë konfigurim të mëtejshëm në bërthamën e OS-it ose në erlang VM. Kushtet për testim:
- erl opts: +A1 +sbtu.
- Testi brenda një nyjeje erlang nis në një laptop me një i7 të vjetër në formën mobile.
- Testet klusteri kalojnë në serverë me rrjet 10G.
- Kodi funksionon në kontejnerë docker. Rrjeti në modalitet NAT.
Kodi i testit:
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.Scenari 1: Testi ekzekutohet në një laptop me një i7 mobil të vjetër. Testi, mesazhet dhe shërbimi ekzekutohen në një nyje brenda një kontejneri docker:
Ciklet sekuenciale 10000 në ~0 sekonda (26987 cikle/s)
Ciklet sekuenciale 20000 në ~1 sekondë (26915 cikle/s)
Ciklet sekuenciale 100000 në ~4 sekonda (26957 cikle/s)
Paralele 2 100000 cikle në ~2 sekonda (44240 cikle/s)
Paralele 4 100000 cikle në ~2 sekonda (53459 cikle/s)
Paralele 10 100000 cikle në ~2 sekonda (52283 cikle/s)
Paralele 100 100000 cikle në ~3 sekonda (49317 cikle/s)Scenari 2: 3 nyje të nisura në makina të ndryshme nën docker (NAT).
Ciklet sekuenciale 10000 në ~1 sekondë (8684 cikle/s)
Ciklet sekuenciale 20000 në ~2 sekonda (8424 cikle/s)
Ciklet sekuenciale 100000 në ~12 sekonda (8655 cikle/s)
Paralele 2 100000 cikle në ~7 sekonda (15160 cikle/s)
Paralele 4 100000 cikle në ~5 sekonda (19133 cikle/s)
Paralele 10 100000 cikle në ~4 sekonda (24399 cikle/s)
Paralele 100 100000 cikle në ~3 sekonda (34517 cikle/s)Në të gjitha rastet, utilizimi i CPU nuk kaloi 250%
Përfundime
Shpresoj që ky cikël të mos duket si një dump i mendimeve dhe përvoja ime do të sjellë vërtet dobi si për kërkuesit e sistemeve të shpërndara ashtu edhe për praktikuesit që janë në fillim të rrugës për të ndërtuar arkitektura të shpërndara për sistemet e tyre të biznesit dhe që shikojnë me interes në Erlang/Elixir, por që dyshojnë nëse ia vlen...
Foto
Vetëm përdoruesit e regjistruar mund të marrin pjesë në anketë. , ju lutem.
Cilat tema duhet të trajtoj më në detaje në kuadër të ciklit "Eksperimenti VTrade"?
Teoria: Tregjet, urdhërat dhe koha e tyre e vlefshmërisë: DAY, GTD, GTC, IOC, FOK, MOO, MOC, LOO, LOC
Libri i urdhrave. Teoria dhe praktika e realizimit të librit me grumbullime
Vizualizimi i tregtisë: Tikë, bara, rezoluta. Si të ruajmë dhe si të bashkëndajmë
Bëkoofisi. Planifikimi dhe zhvillimi. Kontrolli i punonjësve dhe hetimi i incidenteve
API. Po studiojmë se cilat ndërfaqe na nevojiten dhe si t'i realizojmë ato
Ruajtja e informacionit: PostgreSQL, Timescale, Tarantool në sistemet e tregtisë
Reagimi në sistemet e tregtisë
Të tjera. Do të shkruaj në komentet
6 përdorues votuan. 4 përdorues u abstenuan.
Burimi: habr.com
