
Eelmisel Me kĂ€sitlesime reaktiivse arhitektuuri teoreetilisi aluseid. On aeg rÀÀkida andmevoogudest, reaktiivsete Erlang/Elixir sĂŒsteemide elluviimise viisidest ja nende sĂ”numivahetuse mustritest:
- PĂ€ring-vastus
- PĂ€ring-osaline vastus
- Vastus pÀringu alusel
- Avalda-telli
- Pöördprotsesside ĂŒlekandmine
- Ălesannete jaotamine
SOA, MSA ja sÔnumivahetus
SOA ja MSA on sĂŒsteemiarhitektuurid, mis mÀÀravad sĂŒsteemide ĂŒlesehituse reeglid, samas kui sĂ”numivahetus pakub nende rakendamiseks primitiive.
Ma ei soovi propageerida ĂŒhte ega teist sĂŒsteemide arhitektuuri. Ma olen selle poolt, et rakendada konkreetsele projektile ja ettevĂ”ttele maksimaalselt efektiivseid ja kasulikke praktikaid. Millist paradigmat me ei valiks, on sĂŒsteemide plokkide loomine parem Unix-way jĂ€rgi: komponentide minimaalne seos, mis vastutavad eraldi entiteetide eest. API meetodid teevad entiteetidega vĂ”imalikult lihtsaid toiminguid.
SĂ”numivahetus â nagu nimest jĂ€reldub â on sĂ”numi vahendaja. Selle pĂ”hieesmĂ€rk on sĂ”numite vastuvĂ”tt ja edastamine. See vastutab teabe saatmise liideste, loogiliste teabeedastuskanalite loomise, marsruutimise ja tasakaalustamise ning sĂŒsteemitasandi rikete töötlemise eest.
Arendatav sĂ”numivahetus ei pĂŒĂŒa konkureerida rabbitmq-ga ega asendada seda. Selle peamised omadused on:
- Jagatud olemus.
Vahetuspunktid saab luua kÔikides klastriteosades, maksimaalselt lÀhedale koodile, mis neid kasutab. - Lihtsus.
Minimaalne malli koodi ja kasutusmugavuse suund. - Parim jÔudlus.
Me ei pĂŒĂŒa kopeerida rabbitmq funktsionaalsust, vaid eristame ainult arhitektuurilist ja transpordikihti, mille integreerimine OTP-sse on vĂ”imalikult lihtne, minimeerides kulusid. - Paindlikkus.
Iga teenus vÔib hÔlmata mitmeid sÔnumivahetuse mustreid. - TÔrketaluvus, mis on sisustatud disaini.
- Skaleeritavus.
SÔnumivahetus kasvab koos rakendusega. Koormuse suurenedes saate vahetuspunktid eraldi masinatesse viia.
MĂ€rkus. Koodiorganisatsiooni seisukohalt sobivad keerukate sĂŒsteemide jaoks Erlangi/Elixiri metaprojektid hĂ€sti. Kogu projekti kood asub ĂŒhes hoidlas â katuseprojekti all. Sellega samal ajal on mikroteenused maksimaalselt eraldatud ja teevad lihtsaid operatsioone, mis vastavad eraldi entiteedile. Sellise lĂ€henemisega on lihtne toetada kogu sĂŒsteemi API-d, hĂ”lpsasti teha muudatusi ja mugavalt kirjutada ĂŒksus- ja integreerimisteste.
SĂŒsteemi komponendid suhtlevad vahetult vĂ”i lĂ€bi maakleri. Messaging'u seisukohalt on igal teenusel mitu eluetappi:
- Teenuse initsialiseerimine.
Sellel etapil toimub teenuse kÀitamisprotsessi ja sÔltuvuste konfigureerimine ja kÀivitamine. - Vahetuspunkti loomine.
Teenusel vĂ”ib olla staatiline vahetuspunkt, mis on mÀÀratud sĂ”lme konfiguratsioonis, vĂ”i vĂ”ib see luua vahetuspunktid dĂŒnaamiliselt. - Teenuse registreerimine.
Kuna teenus peab teenindama pÀringuid, tuleb see registreerida vahetuspunktis. - Normaalne toimimine.
Teenuse kasulik töö. - Töö lÔpetamine.
Töö lĂ”petamiseks on kaks varianti: regulaarne ja avarii. Regulaarse lĂ”petamise korral katkestatakse teenus vahetuspunktist ja peatub. Avarii korral tĂ€idab messaging ĂŒhe tĂ”rke töötlemise stsenaariumidest.
See tundub ĂŒsna keeruline, kuid koodis pole kĂ”ik nii hirmus. KoodinĂ€idised koos kommentaaridega esitatakse malle kĂ€sitlevas osas veidi hiljem.
Vahetused
Vahetuspunkt on messaging'u protsess, mis rakendab loogikat komponentide vahelise suhtluse jaoks vastavalt sÔnumivahetuse malli. KÔikidel allpool esitatud nÀidistel suhtlevad komponendid vahetuspunktide kaudu, mille kombinatsioon moodustab messaging'u.
SÔnumivahetuse mustrid (MEP-d)
Globaalsete mustrite kohaselt vĂ”ib vahetused jaotada kahepoolsed ja ĂŒhekordsed. Esimene neist eeldab vastust saabunud sĂ”numile, teine aga mitte. Kliendi-serveri arhitektuuris on klassikaliseks nĂ€iteks kahepoolne mustri malli Request-response. Vaatame mustrit ja selle modifikatsioone.
Requestâresponse vĂ”i RPC
RPC-d kasutatakse siis, kui me peame saama vastuse teisest protsessist. See protsess vÔib töötada samal sÔlmel vÔi asuda teisel kontinendil. Allpool on esitatud skeem kliendi ja serverilt vahetuspunkti kaudu.

Kuna messaging on tĂ€ielikult asĂŒnkroonne, jaguneb kliendi vahetus kaheks faasiks:
PĂ€ringu saatmine
messaging:request(Exchange, ResponseMatchingTag, RequestDefinition, HandlerProcess).Exchange â unikaalne vahetuspunkti nimi
ResponseMatchingTag â kohalik silt vastuse töötlemiseks. NĂ€iteks, kui saadetakse mitu sama taotlust erinevatelt kasutajatelt.
RequestDefinition â taotluse keha
HandlerProcess â töötleja PID. See protsess saab serverilt vastuse.Vastuse töötlemine
handle_info(#'$msg'{exchange = EXCHANGE, tag = ResponseMatchingTag,message = ResponsePayload}, State)ResponsePayload â serveri vastus.
Serveri protsess koosneb ka kahest faasist:
- Vahetuspunkti initsialiseerimine
- Saadetud taotluste töötlemine
Ilustreerime seda nÀidet koodiga. Oletame, et peame rakendama lihtsat teenust, mis pakkub ainukest tÀpset aja meetodit.
Serveri kood
Viime teenuse API mÀÀratlemise api.hrl faili:
%% =====================================================
%% entities
%% =====================================================
-record(time, {
unixtime :: non_neg_integer(),
datetime :: binary()
}).
-record(time_error, {
code :: non_neg_integer(),
error :: term()
}).
%% =====================================================
%% methods
%% =====================================================
-record(time_req, {
opts :: term()
}).
-record(time_resp, {
result :: #time{} | #time_error{}
}).MÀÀratleme teenuse kontrolleri time_controller.erl
%% NÀites on nÀidatud vaid oluline kood. Selle lisamine gen_server mallile annab töötava teenuse.
%% gen_serveri initsialiseerimine
init(Args) ->
%% ĂŒhendamine vahetuspunktiga
messaging:monitor_exchange(req_resp, ?EXCHANGE, default, self())
{ok, #{}}.
%% kontakt kaotamise sĂŒndmuse töötlemine vahetuspunktiga. Sama sĂŒndmus esitatakse, kui vahetuspunkt pole veel kĂ€ivitatud.
handle_info(#exchange_die{exchange = ?EXCHANGE}, State) ->
erlang:send(self(), monitor_exchange),
{noreply, State};
%% API töötlemine
handle_info(#time_req{opts = _Opts}, State) ->
messaging:response_once(Client, #time_resp{
result = #time{ unixtime = time_utils:unixtime(now()), datetime = time_utils:iso8601_fmt(now())}
});
{noreply, State};
%% gen_serveri lÔpetamine
terminate(_Reason, _State) ->
messaging:demonitor_exchange(req_resp, ?EXCHANGE, default, self()),
ok.Kliendi kood
Kliendis teenusele pÀringu saatmiseks saab igal pool kutsuda messaging request API-d:
case messaging:request(?EXCHANGE, tag, #time_req{opts = #{}}, self()) of
ok -> ok;
_ -> %% kordamise vÔi ebaÔnnestumise loogika
endJaotatud sĂŒsteemis vĂ”ivad komponentide konfiguratsioonid olla erinevad ning pĂ€ringu ajal ei pruugi messaging kĂ€ivituda vĂ”i teenuse kontroller ei pruugi pĂ€ringut teenindamiseks valmis olla. SeetĂ”ttu peame kontrollima messagingu vastust ja töötlema ebaĂ”nnestumise juhtumi.
PÀrast eduka saatmise korral saab klient teenuselt vastuse vÔi vea.
KÀsitleme mÔlemat juhtumit handle_info-s:
handle_info(#'$msg'{exchange = ?EXCHANGE, tag = tag, message = #time_resp{result = #time{unixtime = Utime}}}, State) ->
?debugVal(Utime),
{noreply, State};
handle_info(#'$msg'{exchange = ?EXCHANGE, tag = tag, message = #time_resp{result = #time_error{code = ErrorCode}}}, State) ->
?debugVal({error, ErrorCode}),
{noreply, State};PĂ€ring-osaline vastus
Suurem sĂ”numite saatmine ei ole soovitatav. See mĂ”jutab kogu sĂŒsteemi reageerimist ja stabiilsust. Kui vastus pĂ€ringule vĂ”tab palju mĂ€lu, on jagamine osadeks hĂ€davajalik.

Tooksin paar sellist juhtumit:
- Komponendid vahetavad binaarandmeid, nĂ€iteks faile. Vastuse jagamine vĂ€ikesteks osadeks aitab tĂ”husalt töötada mis tahes suurusega failidega ning vĂ€ltida mĂ€lu ĂŒlevoolu.
- Loendid. NÀiteks peame valima kÔik kirjed tohutust andmebaasi tabelist ja saatma need teisele komponendile.
Ma kutsun selliseid vastuseid veduriteks. Igatahes on 1024 sĂ”numit, igaĂŒhes 1 MB, parem kui ĂŒksainus sĂ”num, mille suurus on 1 GB.
Erlangi klastris saame tĂ€iendava eelise â koormuse vĂ€hendamine vahetuspunktile ja vĂ”rgu koormusele, kuna vastused suunatakse otse saajale, mööda vahetuspunkti.
Vastus pÀringu alusel
See on ĂŒsna haruldane RPC-mudeli modifikatsioon dialoogisĂŒsteemide loomisel.

Publish-subscribe (andmete jaotamise puu)
SĂŒndmustepĂ”hised sĂŒsteemid toimetavad andmed tarbijatele kohe, kui need on valmis. Nii kalduvad sĂŒsteemid pigem push-mudelile kui pull vĂ”i poll. See omadus vĂ”imaldab mitte raisata ressursse, pidevalt kĂŒsides ja oodates andmeid.
Joonisel on kujutatud sÔnumi edastamise protsessi tarbijatele, kes on registreeritud konkreetse teema jaoks.

Klassikalised nÀited selle mustri kasutamisest on oleku levitamine: mÀngumaailma arvutimÀngudes, turuandmed börsil, kasulik informatsioon andmevoogudes.
Vaatame tellija koodi:
init(_Args) ->
%% registreerume vahetuspunktis, vÔti = key
messaging:subscribe(?SUBSCRIPTION, key, tag, self()),
{ok, #{}}.
handle_info(#exchange_die{exchange = ?SUBSCRIPTION}, State) ->
%% kui vahetuspunkt pole saadaval, proovime uuesti ĂŒhenduda
messaging:subscribe(?SUBSCRIPTION, key, tag, self()),
{noreply, State};
%% töötleme saabunud sÔnumeid
handle_info(#'$msg'{exchange = ?SUBSCRIPTION, message = Msg}, State) ->
?debugVal(Msg),
{noreply, State};
%% tarbija peatamisel katkestame ĂŒhenduse vahetuspunktiga
terminate(_Reason, _State) ->
messaging:unsubscribe(?SUBSCRIPTION, key, tag, self()),
ok.Allikas vÔib kutsuda esile sÔnumi avaldamise mis tahes soovitud kohas:
messaging:publish_message(Exchange, Key, Message).Exchange â vahetuspunkti nimi,
Ava â marsruudi vĂ”ti,
SĂ”num, â koormus,
Pöördprotsesside ĂŒlekandmine

Pub-subâi juurutamine vĂ”imaldab saada logimisele mugava mustri. Allikate ja tarbimise komplekt vĂ”ib olla tĂ€iesti erinev. Joonisel on kujutatud olukorda, kus on ĂŒks tarbija ja palju allikaid.
Ălesannete jaotamise muster.
Pealmost igas projektis tekivad viivitusega töötlemise ĂŒlesanded, nagu aruannete koostamine, teavituste saatmine, andmete saamine vĂ€liste sĂŒsteemide kaudu. Nende ĂŒlesannete tĂ€itmise sĂŒsteemi lĂ€bilaskevĂ”imet saab hĂ”lpsasti skaleerida, lisades töötlejat. Meie ĂŒlesanne on moodustada töötlejate klaster ja jaotada ĂŒlesanded nende vahel vĂ”rdselt.
Vaatleme tekkinud olukordi kolme töötleja nĂ€itel. Juba ĂŒlesannete jagamise etapis tĂ”useb esile Ă”igusemĂ”istmise kĂŒsimus ja töötlejate ĂŒlevoolu vĂ€ltimine. ĂigusemĂ”istmise eest vastutab round-robin jaotamine ning et vĂ€ltida töötlejate ĂŒlevoolu, seame piirangu prefetch_limit. Ăleminekuperioodides prefetch_limit ei luba ĂŒhelgi töötlejal saada kĂ”iki ĂŒlesandeid.
Messsage haldab jĂ€rjekordi ja töötlemise prioriteete. Töötlejad saavad ĂŒlesandeid nende saabumise jĂ€rjekorras. Ălesande tĂ€itmine vĂ”ib lĂ”ppeda kas edukalt vĂ”i ebaĂ”nnestumisega:
messaging:ack(Tack)â kutsutakse esile sĂ”numi eduka töötlemise korral.messaging:nack(Tack)â kutsutakse esile igasugustes erakorralistes olukordades. PĂ€rast ĂŒlesande tagastamist edastab sĂ”numiteenus selle teisele töötlejale.

Olgu öeldud, et kolme ĂŒlesande töötlemisel toimus keeruline ebaĂ”nnestumine: töötleja 1 kukkus pĂ€rast ĂŒlesande saamist kokku, ilma et oleks jĂ”udnud vahetuspunktile midagi teatada. Sellisel juhul edastab vahetuspunkt ĂŒlesande pĂ€rast ack timeout'i möödumist teisele töötlejale. Töötleja 3 keeldus mingil pĂ”hjusel ĂŒlesandest ja saatis nack'i, lĂ”puks lĂ€ks ĂŒlesanne ka teisele töötlejale, kes selle edukalt tĂ€itis.
EelkokkuvÔte
Oleme kĂ€sitlenud jaotatud sĂŒsteemide pĂ”hikivisid ning omandanud nende rakendamise pĂ”hiteadmisi Erlangis/Elixir'is.
Kombineerides pÔhimustreid, on vÔimalik luua keerulisi paradigmadesse probleemide lahendamiseks.
Selle tsĂŒkli lĂ”pposas kĂ€sitleme teenuste korraldust, marsruutimist ja koormuse tasakaalustamist, samuti rÀÀgime sĂŒsteemide skaleeritavuse ja talitluspidevuse praktilisest kĂŒljest.
Teise osa lÔpp.
Foto
Illustratsioonid on koostatud websequencediagrams.com abil.
Allikas: habr.com
