
Tidigare Vi har diskuterat de teoretiska grunderna för reaktiv arkitektur. Det Àr dags att prata om datastreams, implementeringsvÀgar för reaktiva Erlang/Elixir-system och meddelandeutbyte i dem:
- BegÀr-svar
- BegÀr-Chunked Svar
- Svar med BegÀran
- Publicera-prenumerera
- Inverterad Publicera-prenumerera
- Uppgiftsdistribution
SOA, MSA och meddelandeutbyte
SOA, MSA â systemarkitekturer som definierar reglerna för systemuppbyggnad, medan meddelandeutbyte tillhandahĂ„ller primitiva verktyg för deras implementering.
Jag vill inte propagera för en viss systemarkitektur. Jag föresprÄkar att anvÀnda sÄ effektiva och nyttiga metoder som möjligt för ett specifikt projekt och företag. Oavsett vilken paradigm vi vÀljer, Àr det bÀttre att skapa systemblock med tanke pÄ Unix-way: komponenter med minimal bindning som ansvarar för individuella enheter. API-metoder utför de enklaste ÄtgÀrderna med enheterna.
Meddelandeutbyte â som namnet antyder â Ă€r en meddelandebroker. Dess huvudsakliga syfte Ă€r att ta emot och skicka meddelanden. Den ansvarar för grĂ€nssnitten för att skicka information, bildandet av logiska informationsöverföringskanaler inom systemet, routing och belastningsbalansering, samt hantering av fel pĂ„ systemnivĂ„.
Det utvecklade meddelandeutbytet försöker inte konkurrera med rabbitmq eller ersÀtta det. Dess huvuddrag Àr:
- Distribuerbarhet.
Utbytespunkter kan skapas pÄ alla noder i klustret, sÄ nÀra koden som möjligt som anvÀnder dem. - Enkelhet.
Inriktning pÄ minimering av mallkod och anvÀndarvÀnlighet. - BÀsta prestanda.
Vi försöker inte Äterge funktionaliteten hos rabbitmq, utan lyfter fram only den arkitektoniska och transportlagret, som vi enkelt kan integrera i OTP och minimera kostnaderna. - Flexibilitet.
Varje tjÀnst kan kombinera mÄnga mönster för meddelandeutbyte. - FelmotstÄnd, inbÀddat i designen.
- Skalbarhet.
Meddelandeutbytet vÀxer tillsammans med applikationen. Medan belastningen ökar kan man flytta utbytespunkter till separata maskiner.
AnmĂ€rkning. Ur kodorganisations synvinkel passar meta-projekt bra för komplexa system pĂ„ Erlang/Elixir. All kod i projektet finns i ett gemensamt repository â ett paraplyprojekt. Samtidigt Ă€r mikrotjĂ€nster maximalt isolerade och utför enkla operationer som ansvarar för enskilda entiteter. Med detta tillvĂ€gagĂ„ngssĂ€tt Ă€r det lĂ€tt att underhĂ„lla API:et för hela systemet, enkelt att göra Ă€ndringar och bekvĂ€mt att skriva enhets- och integrationstester.
Systemkomponenter interagerar direkt eller via en broker. Ur messaging-synpunkt har varje tjÀnst flera livscykelfaser:
- Initiering av tjÀnsten.
I detta skede sker konfiguration och start av den exekverande tjÀnsteprocessen och dess beroenden. - Skapande av en exchange-punkt.
TjÀnsten kan anvÀnda en statisk exchange-punkt som anges i nodens konfiguration, eller skapa exchange-punkter dynamiskt. - Registrering av tjÀnsten.
För att tjÀnsten ska kunna hantera begÀrningar mÄste den registreras vid exchange-punkten. - Normal funktionalitet.
TjÀnsten utför nytt arbete. - Avslutning av arbetet.
Det finns tvÄ typer av avslutning: normal och akut. Vid normal avslutning kopplar sig tjÀnsten bort frÄn exchange-punkten och stÀngs av. Vid akuta fall utför messaging ett av felhanteringsscenarierna.
Det ser ganska komplicerat ut, men i koden Àr det inte sÄ skrÀmmande. Kodexempel med kommentarer kommer att presenteras i analysen av mönstren lite senare.
Exchanges
En exchange-punkt Àr en messaging-process som implementerar logiken för interaktion med komponenter inom ramen för meddelandeutbytesmönstret. I alla exempel som presenteras nedan interagerar komponenterna via exchange-punkter, vars kombination utgör messaging.
Message exchange patterns (MEPs)
Globalt kan utbytesmallar delas in i tvÄvÀgs och envÀgs. De första innebÀr ett svar pÄ det mottagna meddelandet, de andra gör det inte. Ett klassiskt exempel pÄ en tvÄvÀgs-mall i en klient-server-arkitektur Àr Request-response mönstret. LÄt oss granska mönstret och dess modifieringar.
Requestâresponse eller RPC
RPC anvÀnds nÀr vi behöver fÄ ett svar frÄn en annan process. Denna process kan köras pÄ samma nod eller befinna sig pÄ en annan kontinent. Nedan visas en skiss över interaktionen mellan klienten och servern via messaging.

Eftersom messaging Àr helt asynkront, delas utbytet för klienten in i tvÄ faser:
Skicka begÀran
messaging:request(Exchange, ResponseMatchingTag, RequestDefinition, HandlerProcess).Exchange â ett unikt namn för utbytespunkten
ResponseMatchingTag â en lokal etikett för att bearbeta svaret. Till exempel, i fall av att skicka flera samma förfrĂ„gningar som tillhör olika anvĂ€ndare.
RequestDefinition â kroppen av förfrĂ„gan
HandlerProcess â PID för hanteraren. Denna process kommer att fĂ„ svaret frĂ„n servern.Bearbetning av svaret
handle_info(#'$msg'{exchange = EXCHANGE, tag = ResponseMatchingTag,message = ResponsePayload}, State)ResponsePayload â svaret frĂ„n servern.
För servern bestÄr processen ocksÄ av 2 faser:
- Initiering av utbytespunkten
- Bearbetning av inkommande förfrÄgningar
LÄt oss illustrera detta mönster med kod. Anta att vi behöver implementera en enkel tjÀnst som tillhandahÄller en enda metod för exakt tid.
Serverkod
Vi flyttar definitionen av tjÀnstens API till api.hrl:
%% =====================================================
%% entiteter
%% =====================================================
-record(time, {
unixtime :: non_neg_integer(),
datetime :: binary()
}).
-record(time_error, {
code :: non_neg_integer(),
error :: term()
}).
%% =====================================================
%% metoder
%% =====================================================
-record(time_req, {
opts :: term()
}).
-record(time_resp, {
result :: #time{} | #time_error{}
}).Vi definierar tjÀnstens kontroller i time_controller.erl
%% Exemplet visar endast relevant kod. Genom att infoga det i mönstret gen_server kan vi fÄ en fungerande tjÀnst.
%% initiering av gen_server
init(Args) ->
%% anslutning till utbytespunkten
messaging:monitor_exchange(req_resp, ?EXCHANGE, default, self())
{ok, #{}}.
%% hantering av hÀndelsen för bortfall av kontakt med utbytespunkten. Denna hÀndelse skickas ocksÄ om utbytespunkten Ànnu inte har startat.
handle_info(#exchange_die{exchange = ?EXCHANGE}, State) ->
erlang:send(self(), monitor_exchange),
{noreply, State};
%% hantering av API
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};
%% avslutning av gen_server
terminate(_Reason, _State) ->
messaging:demonitor_exchange(req_resp, ?EXCHANGE, default, self()),
ok.Kundkod
För att skicka en begÀran till tjÀnsten kan messaging begÀran API anropas var som helst i klienten:
case messaging:request(?EXCHANGE, tag, #time_req{opts = #{}}, self()) of
ok -> ok;
_ -> %% upprepa eller fel logik
endI ett distribuerat system kan konfigurationen av komponenter vara mycket varierande, och nÀr begÀran görs kan messaging Ànnu inte ha startat, eller sÄ kan tjÀnstkontrollern inte vara redo att hantera begÀran. DÀrför mÄste vi kontrollera svaret frÄn messaging och hantera avvisningsfallet.
Efter en framgÄngsrik sÀndning kommer klienten att fÄ ett svar eller ett fel frÄn tjÀnsten.
Vi hanterar bÄda fallen i handle_info:
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};BegÀr-Chunked Svar
Det Àr bÀst att undvika att skicka stora meddelanden. Detta pÄverkar responsen och stabiliteten hos hela systemet. Om svaret pÄ en begÀran tar mycket minne, Àr det nödvÀndigt att dela upp det i delar.

Jag ska ge nÄgra exempel pÄ sÄdana fall:
- Komponenter byter binÀra data, exempelvis filer. Att dela upp svaret i smÄ delar gör det möjligt att effektivt hantera filer av alla storlekar utan att fÄ minnesöverfull.
- Listor. Till exempel behöver vi hÀmta alla poster frÄn en stor tabell i databasen och skicka dem till en annan komponent.
Jag kallar sÄdana svar för en lokomotiv. I vilket fall som helst, 1024 meddelanden pÄ 1 MB Àr bÀttre Àn ett enda meddelande som Àr 1 GB.
I en Erlang-kluster fÄr vi ytterligare fördelar - minskad belastning pÄ utbytespunkten och nÀtverket, eftersom svaren skickas direkt till mottagaren utan att gÄ via utbytespunkten.
Svar med BegÀran
Detta Àr en ganska sÀllsynt modifiering av RPC-mönstret för att bygga dialogsystem.

Publicera-prenumerera (datadistributionsstruktur)
HÀndelseorienterade system levererar data till konsumenterna sÄ snart de Àr redo. PÄ sÄ sÀtt Àr systemen mer benÀgna att anvÀnda en push-modell Àn en pull eller poll. Denna egenskap förhindrar slöseri med resurser genom att stÀndigt begÀra och vÀnta pÄ data.
PÄ bilden visas processen för distribution av meddelanden till konsumenter som prenumererat pÄ ett visst Àmne.

Klassiska exempel pÄ anvÀndning av detta mönster Àr distribution av tillstÄnd: spelvÀrldar i datorspel, marknadsdata pÄ börser, anvÀndbar information i dataflöden.
LÄt oss titta pÄ koden för prenumeranten:
init(_Args) ->
%% prenumerera pÄ utbytet, nyckel = key
messaging:subscribe(?SUBSCRIPTION, key, tag, self()),
{ok, #{}}.
handle_info(#exchange_die{exchange = ?SUBSCRIPTION}, State) ->
%% om utbytet inte Àr tillgÀngligt, försök att Äteransluta
messaging:subscribe(?SUBSCRIPTION, key, tag, self()),
{noreply, State};
%% bearbeta inkommande meddelanden
handle_info(#'$msg'{exchange = ?SUBSCRIPTION, message = Msg}, State) ->
?debugVal(Msg),
{noreply, State};
%% vid stopp av konsumenten - koppla bort frÄn utbytet
terminate(_Reason, _State) ->
messaging:unsubscribe(?SUBSCRIPTION, key, tag, self()),
ok.KĂ€lla kan anropa publiceringsfunktionen var som helst:
messaging:publish_message(Exchange, Key, Message).Exchange â namnet pĂ„ utbytespunkten,
Nyckel â routningsnyckel
Message â nyttolast
Inverterad Publicera-prenumerera

Genom att rulla ut pub-sub kan man fÄ ett mönster som Àr bekvÀmt för loggning. MÀngden kÀllor och konsumenter kan variera helt. Bilden visar ett fall med en konsument och mÄnga kÀllor.
Uppgiftfördelningsmönster
I nÀstan varje projekt uppstÄr uppgifter för fördröjd behandling, sÄsom rapportgenerering, aviseringar och datainsamling frÄn externa system. Kapaciteten hos systemet som utför dessa uppgifter kan enkelt skalas genom att lÀgga till bearbetare. Det enda vi behöver göra Àr att skapa en kluster av bearbetare och jÀmnt fördela uppgifterna mellan dem.
LÄt oss övervÀga situationer med 3 bearbetare. Redan i automatiseringssteget uppstÄr frÄgan om rÀttvis fördelning och överbelastning av bearbetarna. RÀttvisan kommer att sÀkerstÀllas genom round-robin fördelning, och för att undvika överbelastningssituationen kommer vi att införa begrÀnsningen prefetch_limit. I övergÄngstillstÄnd prefetch_limit kan en bearbetare inte fÄ alla uppgifter.
Messaging hanterar köer och prioriteringar för behandling. Bearbetarna fÄr uppgifter i takt med att de kommer in. Uppgiftsutförandet kan avslutas antingen med framgÄng eller med ett fel:
messaging:ack(Tack)â anropas vid framgĂ„ngsrik behandling av meddelandetmessaging:nack(Tack)â anropas vid alla onormala situationer. Efter att uppgiften har Ă„terlĂ€mnats kommer messaging att vidarebefordra den till en annan bearbetare.

Anta att det intrÀffade ett komplicerat fel vid bearbetning av tre uppgifter: bearbetare 1 kraschade efter att ha fÄtt uppgiften utan att hinna meddela utbytespunkten. I detta fall kommer utbytespunkten, efter att ack timeout har löpt ut, att överföra uppgiften till en annan bearbetare. Bearbetare 3 avstod frÄn uppgiften av nÄgon anledning och skickade nack, vilket resulterade i att uppgiften Àven överfördes till en annan bearbetare som framgÄngsrikt genomförde den.
PreliminÀrt resultat
Vi har behandlat de grundlÀggande byggstenarna i distribuerade system och fÄtt en grundlÀggande förstÄelse för deras tillÀmpningar i Erlang/Elixir.
Genom att kombinera grundlÀggande mallar kan man bygga komplexa paradigmer för att lösa uppkommande problem.
I den avslutande delen av cykeln kommer vi att diskutera allmÀnna frÄgor om organisation av tjÀnster, ruttningssystem och belastningsbalansering, samt prata om de praktiska aspekterna av skalbarhet och motstÄndskraft hos system.
Slutet av del tvÄ.
Foto
Illustrationer skapade med hjÀlp av websequencediagrams.com
KĂ€lla: habr.com
