Boublokken fan ferspraat applikaasjes. Earste oanpak

Boublokken fan ferspraat applikaasjes. Earste oanpak

Yn de lêste artikel Wy ûndersocht de teoretyske fûneminten fan reaktive arsjitektuer. It is tiid om te praten oer gegevensstreamen, manieren om reaktive Erlang / Elixir-systemen en messagingpatroanen yn te ymplementearjen:

  • Fersyk-antwurd
  • Fersyk-Chunked antwurd
  • Reaksje mei Fersyk
  • Publisearje-abonnearje
  • Inverted Publisearje-abonnearje
  • Taakferdieling

SOA, MSA en Messaging

SOA, MSA binne systeemarsjitektueren dy't de regels definiearje foar it bouwen fan systemen, wylst messaging primitiven leveret foar har ymplemintaasje.

Ik wol dizze of dy systeemarsjitektuer net befoarderje. Ik bin foar it brûken fan de meast effektive en brûkbere praktiken foar in spesifyk projekt en bedriuw. Wat foar paradigma wy ek kieze, it is better om systeemblokken te meitsjen mei it each op 'e Unix-manier: komponinten mei minimale ferbining, ferantwurdlik foar yndividuele entiteiten. API-metoaden útfiere de ienfâldichste mooglike aksjes mei entiteiten.

Messaging is, lykas de namme al fermoeden docht, in berjochtmakelaar. It haaddoel is om berjochten te ûntfangen en te ferstjoeren. It is ferantwurdlik foar de ynterface foar it ferstjoeren fan ynformaasje, de foarming fan logyske kanalen foar it ferstjoeren fan ynformaasje binnen it systeem, routing en balânsjen, lykas ek de ôfhanneling fan fouten op systeemnivo.
De berjochten dy't wy ûntwikkelje besykje net te konkurrearjen mei of te ferfangen rabbitmq. Syn wichtichste skaaimerken:

  • Distribúsje.
    Exchange punten kinne wurde makke op alle kluster knopen, sa ticht mooglik by de koade dy't brûkt se.
  • Ienfâldigens.
    Fokus op it minimalisearjen fan boilerplate-koade en it gemak fan gebrûk.
  • Bettere prestaasjes.
    Wy besykje net de funksjonaliteit fan rabbitmq te werheljen, mar markearje allinich de arsjitektoanyske en transportlaach, dy't wy sa ienfâldich mooglik yn 'e OTP passe, minimalisearje kosten.
  • Fleksibiliteit.
    Elke tsjinst kin in protte útwikselingssjabloanen kombinearje.
  • Resiliency troch ûntwerp.
  • Scalability.
    Messaging groeit mei de applikaasje. As de lading nimt ta, kinne jo ferpleatse de útwikseling punten nei yndividuele masines.

Opmerking. Yn termen fan koade organisaasje, meta-projekten binne goed geskikt foar komplekse Erlang / Elixir systemen. Alle projektkoade leit yn ien repository - in parapluprojekt. Tagelyk binne mikrotsjinsten maksimaal isolearre en útfiere ienfâldige operaasjes dy't ferantwurdlik binne foar in aparte entiteit. Mei dizze oanpak is it maklik om de API fan it heule systeem te behâlden, it is maklik om wizigingen te meitsjen, it is handich om ienheids- en yntegraasjetests te skriuwen.

De systeemkomponinten ynteraksje direkt as fia in makelder. Fanút in berjochtperspektyf hat elke tsjinst ferskate libbensfazen:

  • Service inisjalisaasje.
    Op dit poadium binne it proses en ôfhinklikens dy't de tsjinst útfiere konfigureare en lansearre.
  • It meitsjen fan in útwikseling punt.
    De tsjinst kin brûke in statyske útwikseling punt oantsjutte yn de node konfiguraasje, of meitsje útwikseling punten dynamysk.
  • Service registraasje.
    Om de tsjinst oanfragen te tsjinjen, moat it registrearre wurde op it wikselpunt.
  • Normaal funksjonearjen.
    De tsjinst produsearret nuttich wurk.
  • Ofslúte.
    D'r binne 2 soarten shutdown mooglik: normaal en need. Under normale operaasje wurdt de tsjinst loskeppele fan it wikselpunt en stopet. Yn needsituaasjes fiert messaging ien fan 'e failover-skripts út.

It liket frij yngewikkeld, mar de koade is net sa skriklik. Koadefoarbylden mei opmerkings sille in bytsje letter wurde jûn yn 'e analyze fan sjabloanen.

útwikseling

Exchange punt is in messaging proses dat ymplemintearret de logika fan ynteraksje mei komponinten binnen de messaging sjabloan. Yn alle foarbylden presintearre hjirûnder, de komponinten ynteraksje fia útwikseling punten, de kombinaasje fan dat foarmet messaging.

Berjocht útwikseling patroanen (MEP's)

Globaal kinne útwikselpatroanen wurde ferdield yn twa-wei en ien-wei. De earste betsjuttet in reaksje op in ynkommend berjocht, de lêste net. In klassyk foarbyld fan in twa-wei patroan yn client-server arsjitektuer is it Request-antwurd patroan. Litte wy nei it sjabloan en har oanpassings sjen.

Fersyk-antwurd of RPC

RPC wurdt brûkt as wy moatte ûntfange in antwurd fan in oar proses. Dit proses kin rinne op deselde knooppunt of leit op in oar kontinint. Hjirûnder is in diagram fan de ynteraksje tusken client en server fia messaging.

Boublokken fan ferspraat applikaasjes. Earste oanpak

Sûnt messaging is folslein asynchronous, foar de klant is de útwikseling ferdield yn 2 fazen:

  1. Submit request

    messaging:request(Exchange, ResponseMatchingTag, RequestDefinition, HandlerProcess).

    Útwikseling ‒ unike namme fan it wikselpunt
    ResponseMatchingTag ‒ lokaal label foar it ferwurkjen fan it antwurd. Bygelyks, yn it gefal fan it ferstjoeren fan ferskate identike oanfragen dy't ta ferskate brûkers hearre.
    RequestDefinition - fersyk lichem
    HandlerProcess ‒ PID fan de handler. Dit proses sil in antwurd krije fan de tsjinner.

  2. It ferwurkjen fan it antwurd

    handle_info(#'$msg'{exchange = EXCHANGE, tag = ResponseMatchingTag,message = ResponsePayload}, State)

    ResponsePayload - tsjinner antwurd.

Foar de tsjinner bestiet it proses ek út 2 fazen:

  1. Inisjalisearjen fan it wikselpunt
  2. Ferwurkjen fan ûntfongen oanfragen

Litte wy dit sjabloan yllustrearje mei koade. Litte wy sizze dat wy in ienfâldige tsjinst moatte ymplementearje dy't ien krekte tiidmetoade leveret.

Tsjinner koade

Litte wy de tsjinst API definiearje yn 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{}
}).

Lit ús definiearje de tsjinst controller yn 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.

Client koade

Om in fersyk nei de tsjinst te stjoeren, kinne jo de berjochtfersyk API oeral yn 'e kliïnt skilje:

case messaging:request(?EXCHANGE, tag, #time_req{opts = #{}}, self()) of
    ok -> ok;
    _ -> %% repeat or fail logic
end

Yn in ferspraat systeem kin de konfiguraasje fan komponinten hiel oars wêze en op it momint fan it fersyk kin berjochten noch net begjinne, of de tsjinstkontrôler sil net ree wêze om it fersyk te tsjinjen. Dêrom moatte wy it berjochtenantwurd kontrolearje en it mislearre gefal behannelje.
Nei suksesfol ferstjoeren sil de kliïnt in antwurd of flater krije fan 'e tsjinst.
Litte wy beide gefallen behannelje yn 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};

Fersyk-Chunked antwurd

It is it bêste om gjin grutte berjochten te ferstjoeren. De responsiviteit en stabile wurking fan it hiele systeem hinget hjirfan ôf. As it antwurd op in query in soad ûnthâld nimt, dan is it splitsen yn dielen ferplicht.

Boublokken fan ferspraat applikaasjes. Earste oanpak

Lit my jo in pear foarbylden fan sokke gefallen jaan:

  • De komponinten wikselje binêre gegevens út, lykas bestannen. It brekken fan it antwurd yn lytse dielen helpt jo effisjint te wurkjen mei bestannen fan elke grutte en oerstreamingen fan ûnthâld te foarkommen.
  • Listings. Bygelyks, wy moatte selektearje alle records út in grutte tabel yn de databank en oerdrage se nei in oare komponint.

Ik neam dizze antwurden lokomotyf. Yn alle gefallen binne 1024 berjochten fan 1 MB better as ien berjocht fan 1 GB.

Yn it Erlang-kluster krije wy in ekstra foardiel - it ferminderjen fan de lading op it wikselpunt en it netwurk, om't antwurden fuortendaliks nei de ûntfanger stjoerd wurde, it wikselpunt omgean.

Reaksje mei Fersyk

Dit is in frij seldsume wiziging fan it RPC-patroan foar it bouwen fan dialoochsystemen.

Boublokken fan ferspraat applikaasjes. Earste oanpak

Publisearje-abonnearje (datadistribúsjebeam)

Event-oandreaune systemen leverje se oan konsuminten sa gau as de gegevens klear binne. Sa binne systemen mear gefoelich foar in push-model dan foar in pull- of poll-model. Dizze funksje lit jo boarnen fergrieme troch konstant te freegjen en te wachtsjen op gegevens.
De figuer toant it proses fan it fersprieden fan in berjocht oan konsuminten dy't ynskreaun binne op in spesifyk ûnderwerp.

Boublokken fan ferspraat applikaasjes. Earste oanpak

Klassike foarbylden fan it brûken fan dit patroan binne de ferdieling fan steat: de spielwrâld yn kompjûterspultsjes, merkgegevens oer útwikselingen, nuttige ynformaasje yn datafeeds.

Litte wy nei de abonneekoade sjen:

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.

De boarne kin de funksje neame om in berjocht te publisearjen op elk handich plak:

messaging:publish_message(Exchange, Key, Message).

Útwikseling - namme fan it wikselpunt,
Kaai - routing kaai
Berjocht - lading

Inverted Publisearje-abonnearje

Boublokken fan ferspraat applikaasjes. Earste oanpak

Troch pub-sub út te wreidzjen kinne jo in patroan krije dat handich is foar loggen. De set fan boarnen en konsuminten kin folslein oars wêze. De figuer toant in saak mei ien konsumint en meardere boarnen.

Task distribúsje patroan

Hast elk projekt omfettet útstelde ferwurkingstaken, lykas it generearjen fan rapporten, it leverjen fan notifikaasjes en it opheljen fan gegevens fan systemen fan tredden. De trochfier fan it systeem dat dizze taken útfiert, kin maklik wurde skalearre troch it tafoegjen fan handlers. Alles wat foar ús bliuwt is in kluster fan processors te foarmjen en taken evenredich tusken har te ferdielen.

Litte wy sjen nei de situaasjes dy't ûntsteane mei it foarbyld fan 3 handlers. Sels op it poadium fan taakferdieling ûntstiet de fraach fan earlikens fan distribúsje en oerstreaming fan hannelers. Round-robin-distribúsje sil ferantwurdlik wêze foar earlikens, en om in situaasje fan oerstreaming fan hannelers te foarkommen, sille wy in beheining ynfiere prefetch_limit. Yn oergeande omstannichheden prefetch_limit sil foarkomme dat ien handler in ûntfange alle taken.

Berjochten beheart wachtrijen en ferwurkingsprioriteit. Processors krije taken as se oankomme. De taak kin mei súkses foltôgje of mislearje:

  • messaging:ack(Tack) - neamd as it berjocht mei súkses ferwurke is
  • messaging:nack(Tack) - oproppen yn alle needsituaasjes. Sadree't de taak is werom, sil messaging it trochjaan oan in oare handler.

Boublokken fan ferspraat applikaasjes. Earste oanpak

Stel dat in komplekse mislearring barde by it ferwurkjen fan trije taken: prosessor 1, nei ûntfangst fan de taak, ferûngelokke sûnder tiid te hawwen om wat te rapportearjen oan it útwikselpunt. Yn dit gefal sil it útwikselingspunt de taak oerdrage nei in oare handler nei't de ack-timeout ferrûn is. Om ien of oare reden ferliet handler 3 de taak en stjoerde nack; as gefolch waard de taak ek oerbrocht nei in oare handler dy't it mei súkses foltôge.

Foarriedige gearfetting

Wy hawwe de basisboustiennen fan ferdielde systemen behannele en in basisbegryp krigen fan har gebrûk yn Erlang/Elixir.

Troch basispatroanen te kombinearjen, kinne jo komplekse paradigma's bouwe om opkommende problemen op te lossen.

Yn it lêste diel fan 'e searje sille wy sjen nei algemiene problemen fan it organisearjen fan tsjinsten, routing en balânsjen, en ek prate oer de praktyske kant fan skalberens en fouttolerânsje fan systemen.

Ein fan it twadde diel.

foto Marius Christensen
Yllustraasjes taret mei websequencediagrams.com

Boarne: www.habr.com

Add a comment