In die laaste
- Versoek-reaksie
- Request-Chunked Response
- Reageer met versoek
- Publiseer-teken in
- Omgekeerde Publiseer Teken in
- Taakverdeling
SOA, MSA en boodskappe
SOA, MSA is stelselargitekture wat die reΓ«ls vir die bou van stelsels definieer, terwyl boodskappe primitiewe verskaf vir die implementering daarvan.
Ek wil nie hierdie of daardie stelselargitektuur propageer nie. Ek is vir die toepassing van die mees effektiewe en bruikbare praktyke vir 'n spesifieke projek en besigheid. Watter paradigma ons ook al kies, dit is beter om stelselblokke te skep met die oog op die Unix-manier: komponente met minimale konnektiwiteit, verantwoordelik vir individuele entiteite. API-metodes voer die eenvoudigste aksies uit met entiteite.
Boodskappe - soos die naam aandui - 'n boodskapmakelaar. Die hoofdoel daarvan is om boodskappe te ontvang en te stuur. Dit is verantwoordelik vir die koppelvlakke vir die stuur van inligting, die vorming van logiese kanale vir die oordrag van inligting binne die stelsel, roetering en balansering, sowel as fouthantering op stelselvlak.
Die ontwikkelde boodskappe probeer nie om rabbitmq mee te ding of te vervang nie. Sy hoofkenmerke:
- Verspreiding.
Uitruilpunte kan op alle nodusse van die groep geskep word, so na as moontlik aan die kode wat hulle gebruik. - Eenvoud.
Fokus op die vermindering van boilerplate-kode en gebruiksgemak. - Beter prestasie.
Ons probeer nie om die funksionaliteit van rabbitmq te herhaal nie, maar ons kies slegs die argitektoniese en vervoerlaag, wat ons so eenvoudig as moontlik in OTP inpas, wat koste tot die minimum beperk. - Buigsaamheid.
Elke diens kan baie uitruilsjablone kombineer. - Veerkragtigheid deur ontwerp.
- Skaalbaarheid.
Boodskappe groei saam met die toepassing. Soos die vrag toeneem, kan jy die uitruilpunte na aparte masjiene skuif.
Let daarop. Wat kode-organisasie betref, is metaprojekte goed geskik vir komplekse Erlang/Elixir-stelsels. Alle projekkode is in een bewaarplek - 'n sambreelprojek. Terselfdertyd word mikrodienste soveel as moontlik geΓ―soleer en voer eenvoudige bewerkings uit wat verantwoordelik is vir 'n aparte entiteit. Met hierdie benadering is dit maklik om die API van die hele stelsel in stand te hou, dit is maklik om veranderinge aan te bring, dit is gerieflik om eenheid- en integrasietoetse te skryf.
Stelselkomponente interaksie direk of deur 'n makelaar. Vanuit die posisie van boodskappe het elke diens verskeie lewensfases:
- Diensinisialisering.
Op hierdie stadium vind die konfigurasie en bekendstelling van die proses wat die diens en afhanklikhede uitvoer plaas. - Skep 'n uitruilpunt.
Die diens kan 'n statiese uitruilpunt gebruik wat in die gasheerkonfigurasie gespesifiseer is, of uitruilpunte dinamies skep. - Diensregistrasie.
Ten einde die diens versoeke te bedien, moet dit op die ruilpunt geregistreer wees. - Normale werking.
Die diens doen nuttige werk. - Voltooiing van werk.
Daar is 2 tipes afsluiting: gereelde en noodgevalle. Met 'n gereelde diens ontkoppel dit van die ruilpunt en stop. In noodgevalle voer boodskappe een van die failover-scenario's uit.
Dit lyk nogal ingewikkeld, maar die kode is nie so skrikwekkend nie. Kodevoorbeelde met kommentaar sal 'n bietjie later in die ontleding van sjablone gegee word.
Vervang
'n Uitruilpunt is 'n boodskapproses wat die logika van interaksie met komponente binne die boodskapsjabloon implementeer. In al die voorbeelde hieronder werk die komponente in wisselwerking deur middel van uitruilpunte, waarvan die kombinasie boodskappe vorm.
Boodskapuitruilpatrone (LEP's)
WΓͺreldwyd kan uitruilpatrone in tweesydig en eensydig verdeel word. Eersgenoemde impliseer 'n reaksie op die inkomende boodskap, laasgenoemde nie. 'n Klassieke voorbeeld van 'n tweerigtingpatroon in 'n kliΓ«nt-bediener-argitektuur is die Request-response-patroon. Oorweeg die sjabloon en sy wysigings.
Versoek-reaksie of RPC
RPC word gebruik wanneer ons 'n reaksie van 'n ander proses moet kry. Hierdie proses kan op dieselfde gasheer of op 'n ander kontinent loop. Hieronder is 'n diagram van die interaksie tussen die kliΓ«nt en die bediener deur middel van boodskappe.
Aangesien boodskappe heeltemal asynchronies is, word die uitruil vir die kliΓ«nt in 2 fases verdeel:
-
stuur tans versoek
messaging:request(Exchange, ResponseMatchingTag, RequestDefinition, HandlerProcess).
Ruil β unieke ruilpuntnaam
ResponseMatchingTag β plaaslike etiket vir die verwerking van die antwoord. Byvoorbeeld, in die geval van die stuur van verskeie identiese versoeke wat aan verskillende gebruikers behoort.
Versoek Definisie β versoek liggaam
Hanteerderproses β PID van die hanteerder. Hierdie proses sal 'n antwoord van die bediener ontvang. -
Reaksieverwerking
handle_info(#'$msg'{exchange = EXCHANGE, tag = ResponseMatchingTag,message = ResponsePayload}, State)
ResponsePayload - bediener reaksie.
Vir die bediener bestaan ββdie proses ook uit 2 fases:
- Ruilpuntinisialisering
- Verwerking van inkomende versoeke
Kom ons illustreer hierdie sjabloon met kode. Kom ons sΓͺ dat ons 'n eenvoudige diens moet implementeer wat 'n enkele presiese tydmetode bied.
Bediener kode
Kom ons skuif die diens-API-definisie na api.hrl:
%% =====================================================
%% 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{}
}).
Definieer die diensbeheerder in time_controller.erl
%% Π ΠΏΡΠΈΠΌΠ΅ΡΠ΅ ΠΏΠΎΠΊΠ°Π·Π°Π½ ΡΠΎΠ»ΡΠΊΠΎ Π·Π½Π°ΡΠΈΠΌΡΠΉ ΠΊΠΎΠ΄. ΠΡΡΠ°Π²ΠΈΠ² Π΅Π³ΠΎ Π² ΡΠ°Π±Π»ΠΎΠ½ gen_server ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΠ»ΡΡΠΈΡΡ ΡΠ°Π±ΠΎΡΠΈΠΉ ΡΠ΅ΡΠ²ΠΈΡ.
%% ΠΈΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΡ gen_server
init(Args) ->
%% ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΠΊ ΡΠΎΡΠΊΠ΅ ΠΎΠ±ΠΌΠ΅Π½Π°
messaging:monitor_exchange(req_resp, ?EXCHANGE, default, self())
{ok, #{}}.
%% ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΡΠΎΠ±ΡΡΠΈΡ ΠΏΠΎΡΠ΅ΡΠΈ ΡΠ²ΡΠ·ΠΈ Ρ ΡΠΎΡΠΊΠΎΠΉ ΠΎΠ±ΠΌΠ΅Π½Π°. ΠΡΠΎ ΠΆΠ΅ ΡΠΎΠ±ΡΡΠΈΠ΅ ΠΏΡΠΈΡ
ΠΎΠ΄ΠΈΡ, Π΅ΡΠ»ΠΈ ΡΠΎΡΠΊΠ° ΠΎΠ±ΠΌΠ΅Π½Π° Π΅ΡΠ΅ Π½Π΅ Π·Π°ΠΏΡΡΡΠΈΠ»Π°ΡΡ.
handle_info(#exchange_die{exchange = ?EXCHANGE}, State) ->
erlang:send(self(), monitor_exchange),
{noreply, State};
%% ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ° 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};
%% Π·Π°Π²Π΅ΡΡΠ΅Π½ΠΈΠ΅ ΡΠ°Π±ΠΎΡΡ gen_server
terminate(_Reason, _State) ->
messaging:demonitor_exchange(req_resp, ?EXCHANGE, default, self()),
ok.
KliΓ«nt kode
Om 'n versoek na 'n diens te stuur, kan jy die boodskapversoek-API enige plek op die kliΓ«nt bel:
case messaging:request(?EXCHANGE, tag, #time_req{opts = #{}}, self()) of
ok -> ok;
_ -> %% repeat or fail logic
end
In 'n verspreide stelsel kan die konfigurasie van die komponente baie verskil, en ten tyde van die versoek kan boodskappe nog nie begin nie, of die diensbeheerder sal nie gereed wees om die versoek te bedien nie. Daarom moet ons die boodskapreaksie nagaan en die mislukkingsgeval hanteer.
Na suksesvolle versending aan die kliΓ«nt, sal die diens 'n reaksie of 'n fout ontvang.
Kom ons hanteer beide gevalle in 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};
Request-Chunked Response
Dit is die beste om nie groot boodskappe te stuur nie. Die responsiwiteit en stabiele werking van die hele stelsel hang hiervan af. As die reaksie op 'n navraag baie geheue in beslag neem, is splitsing verpligtend.
Hier is 'n paar voorbeelde van sulke gevalle:
- Komponente ruil binΓͺre data uit, soos lΓͺers. Deur die antwoord in klein dele op te breek, help dit om doeltreffend met lΓͺers van enige grootte te werk en nie geheue oorvloei te vang nie.
- Inskrywings. Byvoorbeeld, ons moet alle rekords uit 'n groot tabel in die databasis kies en dit na 'n ander komponent oordra.
Ek noem sulke reaksies 'n lokomotief. In elk geval, 1024 1MB-boodskappe is beter as 'n enkele 1GB-boodskap.
In die Erlang-kluster kry ons 'n bykomende voordeel - die vermindering van die las op die uitruilpunt en die netwerk, aangesien die antwoorde onmiddellik na die ontvanger gestuur word, wat die uitruilpunt omseil.
Reageer met versoek
Dit is 'n taamlik seldsame wysiging van die RPC-patroon vir die bou van gesprekstelsels.
Publiseer-teken in (dataverspreidingboom)
Gebeurtenisgedrewe stelsels lewer data aan verbruikers sodra dit gereed is. Stelsels is dus meer geneig tot die stootmodel as vir die trek- of peilingsmodel. Hierdie kenmerk laat jou toe om nie hulpbronne te mors deur voortdurend data aan te vra en daarvoor te wag nie.
Die figuur toon die proses van verspreiding van 'n boodskap aan verbruikers wat op 'n spesifieke onderwerp ingeteken is.
Klassieke voorbeelde van die gebruik van hierdie patroon is die verspreiding van staat: die spelwΓͺreld in rekenaarspeletjies, markdata oor uitruilings, nuttige inligting in datavoere.
Oorweeg die intekenaarkode:
init(_Args) ->
%% ΠΏΠΎΠ΄ΠΏΠΈΡΡΠ²Π°Π΅ΠΌΡΡ Π½Π° ΠΎΠ±ΠΌΠ΅Π½Π½ΠΈΠΊ, ΠΊΠ»ΡΡ = key
messaging:subscribe(?SUBSCRIPTION, key, tag, self()),
{ok, #{}}.
handle_info(#exchange_die{exchange = ?SUBSCRIPTION}, State) ->
%% Π΅ΡΠ»ΠΈ ΡΠΎΡΠΊΠ° ΠΎΠ±ΠΌΠ΅Π½Π° Π½Π΅Π΄ΠΎΡΡΡΠΏΠ½Π°, ΡΠΎ ΠΏΡΡΠ°Π΅ΠΌΡΡ ΠΏΠ΅ΡΠ΅ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡΡΡ
messaging:subscribe(?SUBSCRIPTION, key, tag, self()),
{noreply, State};
%% ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°Π΅ΠΌ ΠΏΡΠΈΡΠ΅Π΄ΡΠΈΠ΅ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ
handle_info(#'$msg'{exchange = ?SUBSCRIPTION, message = Msg}, State) ->
?debugVal(Msg),
{noreply, State};
%% ΠΏΡΠΈ ΠΎΡΡΠ°Π½ΠΎΠ²ΠΊΠ΅ ΠΏΠΎΡΡΠ΅Π±ΠΈΡΠ΅Π»Ρ - ΠΎΡΠΊΠ»ΡΡΠ°Π΅ΠΌΡΡ ΠΎΡ ΡΠΎΡΠΊΠΈ ΠΎΠ±ΠΌΠ΅Π½Π°
terminate(_Reason, _State) ->
messaging:unsubscribe(?SUBSCRIPTION, key, tag, self()),
ok.
Die bron kan die publiseerfunksie van die boodskap op enige gerieflike plek noem:
messaging:publish_message(Exchange, Key, Message).
Ruil - naam van die uitruilpunt,
Sleutel β roetesleutel
Boodskap - loonvrag
Omgekeerde Publiseer Teken in
Deur pub-sub te ontplooi, kan u 'n patroon kry wat gerieflik is om aan te meld. Die stel bronne en verbruikers kan heeltemal anders wees. Die figuur toon 'n geval met een verbruiker en baie bronne.
Taakverspreidingspatroon
In byna elke projek is daar take van uitgestelde verwerking, soos om verslae te genereer, kennisgewings te lewer en data van derdepartystelsels te ontvang. Die deurset van 'n stelsel wat hierdie take verrig, word maklik afgeskaal deur verwerkers by te voeg. Al wat vir ons oorbly, is om 'n groep verwerkers te vorm en take eweredig tussen hulle te verdeel.
Oorweeg die situasies wat ontstaan ββdeur die voorbeeld van 3 hanteerders te gebruik. Selfs in die stadium van verspreiding van take, ontstaan ββdie vraag na die regverdigheid van verspreiding en oorloop van hanteerders. Die rondomtalie-verspreiding sal verantwoordelik wees vir regverdigheid, en om 'n situasie van oorloop van hanteerders te vermy, sal ons 'n beperking instel voorafhaal_limiet. In oorgangsmodusse voorafhaal_limiet sal nie toelaat dat een hanteerder alle take ontvang nie.
Boodskappe bestuur rye en verwerkingsprioriteit. Verwerkers ontvang take soos hulle aankom. Die taak kan suksesvol voltooi of misluk:
messaging:ack(Tack)
β gebel in geval van suksesvolle verwerking van die boodskapmessaging:nack(Tack)
β in alle noodsituasies ingeroep. Nadat die taak teruggekeer het, sal boodskappe dit aan 'n ander hanteerder oorgee.
Kom ons neem aan dat tydens die verwerking van drie take, 'n komplekse mislukking plaasgevind het: hanteerder 1, nadat hy die taak ontvang het, het neergestort sonder om tyd te hΓͺ om iets by die uitruilpunt aan te meld. In hierdie geval sal die ruilpunt die taak na 'n ander hanteerder oordra nadat die ack timeout verstryk het. Hanteerder 3 het om een ββof ander rede die taak laat vaar en 'n nak gestuur, gevolglik het die taak ook na 'n ander hanteerder oorgedra wat dit suksesvol voltooi het.
Voorlopige opsomming
Ons het die basiese boublokke van verspreide stelsels afgebreek en 'n basiese begrip gekry van die gebruik daarvan in Erlang/Elixir.
Deur basiese sjablone te kombineer, kan komplekse paradigmas gebou word om opkomende probleme op te los.
In die laaste deel van die reeks sal ons algemene kwessies oor die organisering van dienste, roetering en balansering oorweeg, en ook praat oor die praktiese sy van skaalbaarheid en fouttoleransie van stelsels.
Einde van die tweede deel.
Π€ΠΎΡΠΎ
Illustrasies met vergunning van websequencediagrams.com
Bron: will.com