Konstrubrikoj de distribuitaj aplikoj. Unua alproksimiĝo

Konstrubrikoj de distribuitaj aplikoj. Unua alproksimiĝo

En la lasta artikolo Ni ekzamenis la teoriajn fundamentojn de reaktiva arkitekturo. Estas tempo paroli pri datumfluoj, manieroj efektivigi reaktivajn Erlang/Elixir-sistemojn kaj mesaĝajn ŝablonojn en ili:

  • Peto-respondo
  • Peto-Fraca Respondo
  • Respondo kun Peto
  • Eldoni-aboni
  • Invertita Publiki-aboni
  • Distribuo de taskoj

SOA, MSA kaj Mesaĝado

SOA, MSA estas sistemaj arkitekturoj kiuj difinas la regulojn por konstruado de sistemoj, dum mesaĝado disponigas primitivulojn por ilia efektivigo.

Mi ne volas promocii tiun aŭ alian sisteman arkitekturon. Mi estas por uzi la plej efikajn kaj utilajn praktikojn por specifa projekto kaj komerco. Kian ajn paradigmon ni elektas, estas pli bone krei sistemajn blokojn kun okulo sur la Unikso-maniero: komponantoj kun minimuma konektebleco, respondecaj pri individuaj estaĵoj. API-metodoj plenumas la plej simplajn eblajn agojn kun entoj.

Mesaĝado estas, kiel la nomo sugestas, mesaĝmakleristo. Ĝia ĉefa celo estas ricevi kaj sendi mesaĝojn. Ĝi respondecas pri la interfacoj por sendado de informoj, la formado de logikaj kanaloj por elsendado de informoj ene de la sistemo, vojigo kaj balancado, same kiel mistraktado sur la sistemnivelo.
La mesaĝado, kiun ni disvolvas, ne provas konkuri aŭ anstataŭigi rabbitmq. Ĝiaj ĉefaj trajtoj:

  • Distribuado.
    Interŝanĝpunktoj povas esti kreitaj sur ĉiuj aretnodoj, kiel eble plej proksime al la kodo kiu uzas ilin.
  • Simpleco.
    Fokusu minimumigi kodon kaj facilecon de uzo.
  • Pli bona agado.
    Ni ne provas ripeti la funkciecon de rabbitmq, sed reliefigi nur la arkitekturan kaj transportan tavolon, kiun ni eniras en la OTP kiel eble plej simple, minimumigante kostojn.
  • Fleksebleco.
    Ĉiu servo povas kombini multajn interŝanĝajn ŝablonojn.
  • Eltenemo laŭ dezajno.
  • Skalebleco.
    Mesaĝado kreskas kun la aplikaĵo. Dum la ŝarĝo pliiĝas, vi povas movi la interŝanĝpunktojn al individuaj maŝinoj.

Komento. Koncerne al koda organizo, meta-projektoj taŭgas por kompleksaj Erlang/Elixir-sistemoj. Ĉiu projektkodo troviĝas en unu deponejo - tegmenta projekto. Samtempe, mikroservoj estas maksimume izolitaj kaj plenumas simplajn operaciojn, kiuj respondecas pri aparta ento. Kun ĉi tiu aliro, estas facile konservi la API de la tuta sistemo, estas facile fari ŝanĝojn, estas oportune skribi unuajn kaj integrigajn testojn.

La sistemaj komponantoj interagas rekte aŭ per makleristo. De mesaĝa perspektivo, ĉiu servo havas plurajn vivfazojn:

  • Serva inicialigo.
    En ĉi tiu etapo, la procezo ekzekutanta la servon kaj ĝiajn dependecojn estas agordita kaj lanĉita.
  • Kreante interŝanĝpunkton.
    La servo povas uzi senmovan interŝanĝpunkton specifitan en la noda agordo, aŭ krei interŝanĝpunktojn dinamike.
  • Serva registriĝo.
    Por ke la servo servas petojn, ĝi devas esti registrita ĉe la interŝanĝa punkto.
  • Normala funkciado.
    La servo produktas utilan laboron.
  • Malŝalto.
    Estas 2 specoj de haltigo eblaj: normala kaj kriza. Dum normala operacio, la servo estas malkonektita de la interŝanĝpunkto kaj haltas. En krizaj situacioj, mesaĝado efektivigas unu el la malsukcesaj skriptoj.

Ĝi aspektas sufiĉe komplika, sed la kodo ne estas tiom timiga. Kodekzemploj kun komentoj estos donitaj en la analizo de ŝablonoj iom poste.

interŝanĝoj

Interŝanĝpunkto estas mesaĝa procezo kiu efektivigas la logikon de interago kun komponantoj ene de la mesaĝa ŝablono. En ĉiuj ekzemploj prezentitaj sube, la komponantoj interagas per interŝanĝpunktoj, kies kombinaĵo formas mesaĝadon.

Mesaĝaj interŝanĝaj ŝablonoj (MEPoj)

Tutmonde, interŝanĝaj ŝablonoj povas esti dividitaj en dudirektan kaj unudirektan. La unuaj implicas respondon al envenanta mesaĝo, la lastaj ne faras. Klasika ekzemplo de dudirekta ŝablono en klient-servila arkitekturo estas la Peto-responda ŝablono. Ni rigardu la ŝablonon kaj ĝiajn modifojn.

Peto-respondo aŭ RPC

RPC estas uzata kiam ni bezonas ricevi respondon de alia procezo. Ĉi tiu procezo povas funkcii sur la sama nodo aŭ situanta sur malsama kontinento. Malsupre estas diagramo de la interago inter kliento kaj servilo per mesaĝado.

Konstrubrikoj de distribuitaj aplikoj. Unua alproksimiĝo

Ĉar mesaĝado estas tute nesinkrona, por la kliento la interŝanĝo estas dividita en 2 fazojn:

  1. Submetu peton

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

    interŝanĝo ‒ unika nomo de la interŝanĝopunkto
    ResponseMatchingTag ‒ loka etikedo por prilabori la respondon. Ekzemple, en la kazo de sendado de pluraj identaj petoj apartenantaj al malsamaj uzantoj.
    PetoDifino - peto korpo
    HandlerProcess ‒ PID de la prizorganto. Ĉi tiu procezo ricevos respondon de la servilo.

  2. Prilaborado de la respondo

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

    ResponsePayload - servila respondo.

Por la servilo, la procezo ankaŭ konsistas el 2 fazoj:

  1. Komencante la interŝanĝpunkton
  2. Prilaborado de ricevitaj petoj

Ni ilustru ĉi tiun ŝablonon per kodo. Ni diru, ke ni devas efektivigi simplan servon, kiu provizas ununuran ĝustan tempon.

Servila kodo

Ni difinu la servon API en 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{}
}).

Ni difinu la servoregilon en 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.

Klientokodo

Por sendi peton al la servo, vi povas voki la mesaĝan peton API ie ajn en la kliento:

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

En distribuita sistemo, la agordo de komponantoj povas esti tre malsama kaj en la momento de la peto, mesaĝado eble ankoraŭ ne komenciĝas, aŭ la serva regilo ne estos preta servi la peton. Tial ni devas kontroli la mesaĝan respondon kaj trakti la fiaskan kazon.
Post sukcesa sendo, la kliento ricevos respondon aŭ eraron de la servo.
Ni traktu ambaŭ kazojn en 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};

Peto-Fraca Respondo

Plej bone eviti sendi grandegajn mesaĝojn. De tio dependas la respondeco kaj stabila funkciado de la tuta sistemo. Se la respondo al demando okupas multe da memoro, tiam dividi ĝin en partojn estas deviga.

Konstrubrikoj de distribuitaj aplikoj. Unua alproksimiĝo

Mi donu al vi kelkajn ekzemplojn de tiaj kazoj:

  • La komponantoj interŝanĝas binarajn datumojn, kiel dosierojn. Rompi la respondon en malgrandajn partojn helpas vin labori efike kun dosieroj de ajna grandeco kaj eviti memorajn superfluojn.
  • Listoj. Ekzemple, ni devas elekti ĉiujn registrojn el grandega tabelo en la datumbazo kaj transdoni ilin al alia komponanto.

Mi nomas ĉi tiujn respondojn lokomotivo. Ĉiukaze, 1024 mesaĝoj de 1 MB estas pli bonaj ol ununura mesaĝo de 1 GB.

En la Erlang-areo, ni ricevas plian avantaĝon - reduktante la ŝarĝon sur la interŝanĝpunkto kaj la reto, ĉar respondoj estas tuj senditaj al la ricevanto, preterirante la interŝanĝpunkton.

Respondo kun Peto

Ĉi tio estas sufiĉe malofta modifo de la RPC-ŝablono por konstrui dialogsistemojn.

Konstrubrikoj de distribuitaj aplikoj. Unua alproksimiĝo

Publiki-aboni (datuma distribuarbo)

Event-movitaj sistemoj liveras ilin al konsumantoj tuj kiam la datumoj estas pretaj. Tiel, sistemoj estas pli emaj al puŝmodelo ol al tiro aŭ balotmodelo. Ĉi tiu funkcio ebligas al vi eviti malŝpari rimedojn konstante petante kaj atendante datumojn.
La figuro montras la procezon de distribuado de mesaĝo al konsumantoj abonitaj al specifa temo.

Konstrubrikoj de distribuitaj aplikoj. Unua alproksimiĝo

Klasikaj ekzemploj de uzado de ĉi tiu ŝablono estas la distribuo de ŝtato: la ludmondo en komputilaj ludoj, merkataj datumoj pri interŝanĝoj, utilaj informoj en datumfluoj.

Ni rigardu la abonan kodon:

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.

La fonto povas voki la funkcion por publikigi mesaĝon en iu ajn oportuna loko:

messaging:publish_message(Exchange, Key, Message).

interŝanĝo - nomo de la interŝanĝpunkto,
ŝlosilo - vojŝlosilo
Mesaĝo - utila ŝarĝo

Invertita Publiki-aboni

Konstrubrikoj de distribuitaj aplikoj. Unua alproksimiĝo

Pligrandigante pub-sub, vi povas akiri ŝablonon konvenan por registri. La aro de fontoj kaj konsumantoj povas esti tute malsama. La figuro montras kazon kun unu konsumanto kaj multoblaj fontoj.

Taskodistribua ŝablono

Preskaŭ ĉiu projekto implikas prokrastitajn taskojn, kiel ekzemple generado de raportoj, liverado de sciigoj kaj reakiro de datumoj de triaj sistemoj. La trairo de la sistemo plenumanta ĉi tiujn taskojn povas esti facile skalita aldonante prizorgantojn. Restas al ni nur formi aron da procesoroj kaj egale distribui taskojn inter ili.

Ni rigardu la situaciojn kiuj ekestas uzante la ekzemplon de 3 prizorgantoj. Eĉ en la stadio de distribuo de taskoj, aperas la demando pri justeco de distribuo kaj superfluo de prizorgantoj. Ĉirkaŭ-subskribolista distribuo respondecos pri justeco, kaj por eviti situacion de superfluo de prizorgantoj, ni enkondukos limigon. prefetch_limo. En pasemaj kondiĉoj prefetch_limo malhelpos unu prizorganton ricevi ĉiujn taskojn.

Mesaĝado administras atendovicojn kaj prilaboran prioritaton. Procesoroj ricevas taskojn kiam ili alvenas. La tasko povas plenumi sukcese aŭ malsukcesi:

  • messaging:ack(Tack) - vokita se la mesaĝo estas sukcese procesita
  • messaging:nack(Tack) - vokis en ĉiuj krizaj situacioj. Post kiam la tasko estas resendita, mesaĝado transdonos ĝin al alia prizorganto.

Konstrubrikoj de distribuitaj aplikoj. Unua alproksimiĝo

Supozu ke kompleksa fiasko okazis dum prilaborado de tri taskoj: procesoro 1, post ricevado de la tasko, kraŝis sen havi tempon raporti ion ajn al la interŝanĝpunkto. En ĉi tiu kazo, la interŝanĝpunkto translokigos la taskon al alia prizorganto post kiam la ack-tempo eksvalidiĝis. Ial, prizorganto 3 prirezignis la taskon kaj sendis nakon; kiel rezulto, la tasko ankaŭ estis transdonita al alia prizorganto kiu sukcese kompletigis ĝin.

Prepara resumo

Ni kovris la bazajn konstrubriketojn de distribuitaj sistemoj kaj akiris bazan komprenon pri ilia uzo en Erlang/Elixir.

Kombinante bazajn ŝablonojn, vi povas konstrui kompleksajn paradigmojn por solvi emerĝantajn problemojn.

En la fina parto de la serio, ni rigardos ĝeneralajn aferojn pri organizado de servoj, vojigo kaj ekvilibro, kaj ankaŭ parolos pri la praktika flanko de skaleblo kaj misfunkciado de sistemoj.

Fino de la dua parto.

Foto Marius Christensen
Ilustraĵoj preparitaj uzante websequencediagrams.com

fonto: www.habr.com

Aldoni komenton