Građevinski blokovi distribuiranih aplikacija. Prvi pristup

Građevinski blokovi distribuiranih aplikacija. Prvi pristup

U poslednjem članak analizirali smo teorijske osnove reaktivne arhitekture. Vrijeme je da razgovaramo o tokovima podataka, načinima implementacije reaktivnih Erlang/Elixir sistema i obrascima razmjene poruka u njima:

  • Zahtjev-odgovor
  • Request-Cunked Response
  • Odgovor sa zahtjevom
  • Objavi-pretplati se
  • Obrnuto Objavi Pretplati se
  • Distribucija zadataka

SOA, MSA i razmjena poruka

SOA, MSA su sistemske arhitekture koje definišu pravila za izgradnju sistema, dok razmena poruka obezbeđuje primitive za njihovu implementaciju.

Ne želim da propagiram ovu ili onu arhitekturu sistema. Ja sam za primjenu najefikasnijih i najkorisnijih praksi za određeni projekat i posao. Koju god paradigmu da izaberemo, bolje je kreirati sistemske blokove s pogledom na Unix način: komponente sa minimalnom konektivnošću, odgovorne za pojedinačne entitete. API metode izvode najjednostavnije radnje s entitetima.

Messaging - kao što ime implicira - posrednik poruka. Njegova glavna svrha je primanje i slanje poruka. Odgovoran je za interfejse za slanje informacija, formiranje logičkih kanala za prenos informacija unutar sistema, rutiranje i balansiranje, kao i rukovanje kvarovima na nivou sistema.
Razvijena razmjena poruka ne pokušava se natjecati sa ili zamijeniti rabbitmq. Njegove glavne karakteristike:

  • Distribucija.
    Tačke razmjene mogu se kreirati na svim čvorovima klastera, što je bliže moguće kodu koji ih koristi.
  • Jednostavnost.
    Fokusirajte se na minimiziranje standardnog koda i jednostavnost korištenja.
  • Bolje performanse.
    Ne pokušavamo da ponovimo funkcionalnost rabbitmq-a, već biramo samo arhitektonski i transportni sloj, koji što jednostavnije uklapamo u OTP, minimizirajući troškove.
  • Fleksibilnost.
    Svaka usluga može kombinovati mnogo šablona razmene.
  • Otpornost po dizajnu.
  • Skalabilnost.
    Razmjena poruka raste s aplikacijom. Kako se opterećenje povećava, možete premjestiti točke zamjene na odvojene mašine.

Napomena. U smislu organizacije koda, meta-projekti su veoma pogodni za kompleksne Erlang/Elixir sisteme. Sav kod projekta je u jednom spremištu - krovnom projektu. U isto vrijeme, mikroservise su izolovane što je više moguće i obavljaju jednostavne operacije koje su odgovorne za poseban entitet. Ovakvim pristupom lako je održavati API cijelog sistema, lako je unositi promjene, zgodno je pisati jedinične i integracijske testove.

Komponente sistema komuniciraju direktno ili preko posrednika. Sa pozicije razmjene poruka, svaka usluga ima nekoliko životnih faza:

  • Inicijalizacija usluge.
    U ovoj fazi se odvija konfiguracija i pokretanje procesa koji izvršava uslugu i zavisnosti.
  • Kreirajte razmjenu.
    Usluga može koristiti statičku točku razmjene navedenu u konfiguraciji hosta ili dinamički kreirati razmjenske točke.
  • Registracija servisa.
    Da bi usluga služila zahtjeve, mora biti registrirana na mjestu razmjene.
  • Normalan rad.
    Usluga radi koristan posao.
  • Završetak radova.
    Postoje 2 vrste isključenja: redovno i hitno. Redovnim servisom se isključuje sa centrale i zaustavlja. U hitnim slučajevima, razmjena poruka izvršava jedan od scenarija prelaska na grešku.

Izgleda prilično komplikovano, ali kod nije toliko strašno. Primjeri koda s komentarima bit će dati u analizi šablona nešto kasnije.

razmjena

Tačka razmjene je proces razmjene poruka koji implementira logiku interakcije sa komponentama unutar predloška za razmjenu poruka. U svim primjerima u nastavku, komponente stupaju u interakciju preko razmjenskih tačaka, čija kombinacija formira razmjenu poruka.

Obrasci razmjene poruka (MEP)

U globalu, obrasci razmjene mogu se podijeliti na dvostrane i jednostrane. Prvi podrazumijevaju odgovor na dolaznu poruku, drugi ne. Klasičan primjer dvosmjernog uzorka u arhitekturi klijent-server je obrazac zahtjev-odgovor. Razmotrite predložak i njegove modifikacije.

Zahtjev-odgovor ili RPC

RPC se koristi kada trebamo dobiti odgovor od drugog procesa. Ovaj proces može biti pokrenut na istom hostu ili na drugom kontinentu. Ispod je dijagram interakcije između klijenta i servera putem razmjene poruka.

Građevinski blokovi distribuiranih aplikacija. Prvi pristup

Pošto je razmjena poruka potpuno asinhrona, razmjena za klijenta je podijeljena u 2 faze:

  1. Submit request

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

    razmjena ‒ jedinstveni naziv tačke razmene
    ResponseMatchingTag ‒ lokalna oznaka za obradu odgovora. Na primjer, u slučaju slanja nekoliko identičnih zahtjeva koji pripadaju različitim korisnicima.
    Definicija zahtjeva ‒ tijelo zahtjeva
    HandlerProcess ‒ PID rukovaoca. Ovaj proces će dobiti odgovor od servera.

  2. Obrada odgovora

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

    ResponsePayload - odgovor servera.

Za server, proces se takođe sastoji od 2 faze:

  1. Inicijalizacija razmjene točke
  2. Obrada dolaznih zahtjeva

Ilustrujmo ovaj šablon kodom. Recimo da trebamo implementirati jednostavnu uslugu koja pruža jednu metodu tačnog vremena.

Serverski kod

Premjestimo definiciju API-ja usluge 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{}
}).

Definirajte servisni kontroler u 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.

Klijentski kod

Da biste poslali zahtjev usluzi, možete pozvati API zahtjeva za razmjenu poruka bilo gdje na klijentu:

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

U distribuiranom sistemu, konfiguracija komponenti može biti vrlo različita, a u vrijeme zahtjeva, razmjena poruka možda još nije započela ili servisni kontroler neće biti spreman da posluži zahtjevu. Stoga moramo provjeriti odgovor na razmjenu poruka i riješiti slučaj kvara.
Nakon uspješnog slanja klijentu, servis će dobiti odgovor ili grešku.
Obradimo oba slučaja u 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-Cunked Response

Najbolje je izbjegavati slanje velikih poruka. Od toga zavisi odzivnost i stabilan rad čitavog sistema. Ako odgovor na upit zauzima puno memorije, onda je podjela obavezno.

Građevinski blokovi distribuiranih aplikacija. Prvi pristup

Evo nekoliko primjera takvih slučajeva:

  • Komponente razmjenjuju binarne podatke, kao što su datoteke. Razbijanje odgovora na male dijelove pomaže u efikasnom radu s datotekama bilo koje veličine i ne uhvatiti prepune memorije.
  • Oglasi. Na primjer, moramo odabrati sve zapise iz ogromne tablice u bazi podataka i proslijediti ih drugoj komponenti.

Takve odgovore nazivam lokomotivom. U svakom slučaju, 1024 poruke od 1 MB su bolje od jedne poruke od 1 GB.

U Erlang klasteru dobijamo dodatnu pogodnost - smanjenje opterećenja na tački razmene i mreži, jer se odgovori odmah šalju primaocu, zaobilazeći tačku razmene.

Odgovor sa zahtjevom

Ovo je prilično rijetka modifikacija RPC obrasca za izgradnju konverzacijskih sistema.

Građevinski blokovi distribuiranih aplikacija. Prvi pristup

Objavi-pretplati se (stablo distribucije podataka)

Sistemi vođeni događajima isporučuju podatke potrošačima čim budu spremni. Dakle, sistemi su skloniji push modelu nego modelu povlačenja ili anketiranja. Ova funkcija vam omogućava da ne trošite resurse stalnim traženjem i čekanjem podataka.
Slika prikazuje proces distribucije poruke potrošačima koji su pretplaćeni na određenu temu.

Građevinski blokovi distribuiranih aplikacija. Prvi pristup

Klasični primjeri korištenja ovog obrasca su distribucija stanja: svijet igre u kompjuterskim igrama, tržišni podaci na berzama, korisne informacije u izvorima podataka.

Uzmite u obzir pretplatnički kod:

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.

Izvor može pozvati funkciju objave poruke na bilo kojem prikladnom mjestu:

messaging:publish_message(Exchange, Key, Message).

razmjena - naziv mjesta razmjene,
ključ ‒ ključ za rutiranje
Poruka - nosivost

Obrnuto Objavi Pretplati se

Građevinski blokovi distribuiranih aplikacija. Prvi pristup

Uvođenjem pub-sub-a možete dobiti obrazac koji je zgodan za logovanje. Skup izvora i potrošača može biti potpuno različit. Na slici je prikazan slučaj sa jednim potrošačem i više izvora.

Obrazac distribucije zadataka

U gotovo svakom projektu postoje zadaci odgođene obrade, kao što su generiranje izvještaja, dostavljanje obavijesti i primanje podataka od sistema trećih strana. Propusnost sistema koji obavlja ove zadatke lako se skalira dodavanjem procesora. Sve što nam preostaje je da formiramo klaster procesora i ravnomerno rasporedimo zadatke između njih.

Razmotrite situacije koje nastaju na primjeru 3 rukovatelja. Čak iu fazi distribucije zadataka postavlja se pitanje pravednosti distribucije i prelivanja rukovalaca. Za pravednost će biti odgovorna kružna distribucija, a kako bismo izbjegli situaciju preopterećenja rukovatelja, uvest ćemo ograničenje prefetch_limit. U prijelaznim modovima prefetch_limit neće dozvoliti jednom rukovaocu da primi sve zadatke.

Razmjena poruka upravlja redovima čekanja i prioritetom obrade. Procesori primaju zadatke čim stignu. Zadatak se može završiti uspješno ili neuspješno:

  • messaging:ack(Tack) ‒ poziva se u slučaju uspješne obrade poruke
  • messaging:nack(Tack) ‒ pozivati ​​u svim hitnim situacijama. Nakon što se zadatak vrati, poruka će ga proslijediti drugom rukovaocu.

Građevinski blokovi distribuiranih aplikacija. Prvi pristup

Pretpostavimo da je prilikom obrade tri zadatka došlo do složenog kvara: rukovalac 1, nakon što je primio zadatak, pao je bez vremena da bilo šta prijavi tački razmene. U ovom slučaju, točka razmjene će prenijeti posao drugom rukovaocu nakon što istekne vrijeme čekanja za potvrdu. Rukovalac 3 je iz nekog razloga napustio zadatak i poslao nack, kao rezultat toga, zadatak je takođe prošao drugom rukovaocu koji ga je uspešno završio.

Preliminarni sažetak

Rastavili smo osnovne građevne blokove distribuiranih sistema i stekli osnovno razumevanje njihove upotrebe u Erlang/Elixir.

Kombinacijom osnovnih šablona mogu se izgraditi složene paradigme za rješavanje novih problema.

U završnom dijelu ciklusa razmotrit ćemo opšta pitanja organizacije servisa, rutiranja i balansiranja, a također ćemo govoriti o praktičnoj strani skalabilnosti i tolerancije grešaka sistema.

Kraj drugog dijela.

fotografija Marius Christensen
Ilustracije ljubaznošću websequencediagrams.com

izvor: www.habr.com

Dodajte komentar