Sastavni dijelovi distribuiranih aplikacija. Prvi pristup

Sastavni dijelovi distribuiranih aplikacija. Prvi pristup

U posljednjih članak Ispitali smo teorijske temelje reaktivne arhitekture. Vrijeme je da razgovaramo o protoku podataka, načinima implementacije reaktivnih Erlang/Elixir sustava i obrascima poruka u njima:

  • Zahtjev-odgovor
  • Odgovor podijeljen na zahtjev
  • Odgovor sa zahtjevom
  • Objavi-pretplati se
  • Obrnuto Objavi-pretplati se
  • Raspodjela zadataka

SOA, MSA i slanje poruka

SOA, MSA su sistemske arhitekture koje definiraju pravila za izgradnju sustava, dok poruka daje primitive za njihovu implementaciju.

Ne želim promovirati ovu ili onu arhitekturu sustava. Ja sam za korištenje najučinkovitijih i najkorisnijih praksi za određeni projekt i posao. Koju god paradigmu odabrali, bolje je kreirati sistemske blokove s pogledom na Unix-način: komponente s minimalnom povezanošću, odgovorne za pojedinačne entitete. API metode izvode najjednostavnije moguće radnje s entitetima.

Messaging je, kao što ime sugerira, posrednik poruka. Njegova glavna svrha je primanje i slanje poruka. Odgovoran je za sučelja za slanje informacija, formiranje logičkih kanala za prijenos informacija unutar sustava, usmjeravanje i balansiranje, kao i rukovanje greškama na razini sustava.
Poruke koje razvijamo ne pokušavaju se natjecati ili zamijeniti rabbitmq. Njegove glavne karakteristike:

  • Distribucija.
    Točke razmjene mogu se stvoriti na svim čvorovima klastera, što bliže kodu koji ih koristi.
  • Jednostavnost.
    Usredotočite se na minimiziranje standardnog koda i jednostavnost korištenja.
  • Bolja izvedba.
    Ne pokušavamo ponoviti funkcionalnost rabbitmq-a, već ističemo samo arhitektonski i transportni sloj koji što jednostavnije uklapamo u OTP, minimizirajući troškove.
  • Fleksibilnost.
    Svaka usluga može kombinirati mnoge predloške za razmjenu.
  • Otpornost prema dizajnu.
  • Skalabilnost.
    Razmjena poruka raste s aplikacijom. Kako se opterećenje povećava, možete premjestiti točke razmjene na pojedinačne strojeve.

Napomena. U smislu organizacije koda, meta-projekti su prikladni za složene Erlang/Elixir sustave. Sav kod projekta nalazi se u jednom repozitoriju - krovnom projektu. Istovremeno, mikroservisi su maksimalno izolirani i izvode jednostavne operacije koje su odgovorne za zaseban entitet. Ovakvim pristupom lako je održavati API cijelog sustava, lako je unositi izmjene, zgodno je pisati jedinične i integracijske testove.

Komponente sustava međusobno djeluju izravno ili preko brokera. Iz perspektive slanja poruka, svaka usluga ima nekoliko životnih faza:

  • Inicijalizacija usluge.
    U ovoj fazi konfiguriraju se i pokreću proces i ovisnosti koje izvršavaju uslugu.
  • Stvaranje mjesta razmjene.
    Usluga može koristiti statičku točku razmjene navedenu u konfiguraciji čvora ili dinamički stvoriti točke razmjene.
  • Registracija usluge.
    Da bi usluga mogla posluživati ​​zahtjeve, mora biti registrirana na mjenjačkom mjestu.
  • Normalno funkcioniranje.
    Usluga proizvodi koristan rad.
  • Završetak radova.
    Moguće su 2 vrste isključenja: normalno i hitno. Tijekom normalnog rada, usluga se isključuje iz mjesta razmjene i zaustavlja se. U hitnim situacijama razmjena poruka izvršava jednu od skripti za nadogradnju.

Izgleda prilično komplicirano, ali šifra nije toliko zastrašujuća. Primjeri koda s komentarima bit će dati u analizi predložaka malo kasnije.

Razmjena

Točka razmjene je proces razmjene poruka koji implementira logiku interakcije s komponentama unutar predloška za razmjenu poruka. U svim primjerima prikazanim u nastavku, komponente međusobno djeluju kroz točke razmjene, čija kombinacija tvori razmjenu poruka.

Obrasci razmjene poruka (MEPs)

Globalno, obrasci razmjene mogu se podijeliti na dvosmjerne i jednosmjerne. Prvi podrazumijevaju odgovor na dolaznu poruku, drugi ne. Klasičan primjer dvosmjernog obrasca u arhitekturi klijent-poslužitelj je obrazac zahtjev-odgovor. Pogledajmo predložak i njegove izmjene.

Zahtjev-odgovor ili RPC

RPC se koristi kada trebamo primiti odgovor od drugog procesa. Ovaj se proces može izvoditi na istom čvoru ili se nalazi na drugom kontinentu. Ispod je dijagram interakcije između klijenta i poslužitelja putem poruka.

Sastavni dijelovi distribuiranih aplikacija. Prvi pristup

Budući da je slanje poruka potpuno asinkrono, za klijenta je razmjena podijeljena u 2 faze:

  1. slanje zahtjeva

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

    razmjena ‒ jedinstveni naziv mjenjačkog mjesta
    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 rukovatelja. Ovaj proces će dobiti odgovor od poslužitelja.

  2. Obrada odgovora

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

    ResponsePayload - odgovor poslužitelja.

Za poslužitelj se proces također sastoji od 2 faze:

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

Ilustrirajmo ovaj predložak kodom. Recimo da trebamo implementirati jednostavnu uslugu koja pruža jednu metodu točnog vremena.

Kod poslužitelja

Definirajmo API usluge u 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{}
}).

Definirajmo kontroler usluge 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.

Šifra klijenta

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

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

U distribuiranom sustavu, konfiguracija komponenti može biti vrlo različita i u vrijeme zahtjeva razmjena poruka možda još nije započela ili kontroler usluge neće biti spreman za servisiranje zahtjeva. Stoga moramo provjeriti odgovor na poruke i riješiti slučaj kvara.
Nakon uspješnog slanja, klijent će dobiti odgovor ili grešku servisa.
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};

Odgovor podijeljen na zahtjev

Najbolje je izbjegavati slanje velikih poruka. O tome ovisi odzivnost i stabilan rad cijelog sustava. Ako odgovor na upit zauzima puno memorije, tada je njegovo dijeljenje na dijelove obavezno.

Sastavni dijelovi distribuiranih aplikacija. Prvi pristup

Navest ću vam nekoliko primjera takvih slučajeva:

  • Komponente razmjenjuju binarne podatke, poput datoteka. Razbijanje odgovora na male dijelove pomaže vam da učinkovito radite s datotekama bilo koje veličine i izbjegavate prekoračenje memorije.
  • oglasi. Na primjer, moramo odabrati sve zapise iz ogromne tablice u bazi podataka i prenijeti ih u drugu komponentu.

Ove odgovore nazivam lokomotivom. U svakom slučaju, bolje su 1024 poruke od 1 MB nego jedna poruka od 1 GB.

U Erlang klasteru dobivamo dodatnu pogodnost - smanjenje opterećenja razmjenske točke i mreže, budući da se odgovori odmah šalju primatelju, zaobilazeći razmjensku točku.

Odgovor sa zahtjevom

Ovo je prilično rijetka modifikacija RPC uzorka za izgradnju dijaloških sustava.

Sastavni dijelovi distribuiranih aplikacija. Prvi pristup

Objavi-pretplati se (stablo distribucije podataka)

Sustavi vođeni događajima isporučuju ih potrošačima čim su podaci spremni. Stoga su sustavi skloniji push modelu nego modelu pull ili ankete. Ova vam značajka omogućuje izbjegavanje rasipanja resursa stalnim traženjem i čekanjem podataka.
Slika prikazuje proces distribucije poruke potrošačima koji su pretplaćeni na određenu temu.

Sastavni dijelovi distribuiranih aplikacija. Prvi pristup

Klasični primjeri korištenja ovog uzorka su distribucija stanja: svijet igre u računalnim igrama, tržišni podaci na burzama, korisne informacije u izvorima podataka.

Pogledajmo 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 za objavljivanje poruke na bilo kojem prikladnom mjestu:

messaging:publish_message(Exchange, Key, Message).

razmjena - naziv mjenjačkog mjesta,
Ključ - ključ usmjeravanja
Poruka - nosivost

Obrnuto Objavi-pretplati se

Sastavni dijelovi distribuiranih aplikacija. Prvi pristup

Proširivanjem pub-sub, možete dobiti uzorak pogodan za prijavu. Skup izvora i potrošača može biti potpuno drugačiji. Slika prikazuje slučaj s jednim potrošačem i više izvora.

Obrazac raspodjele zadataka

Gotovo svaki projekt uključuje odgođene zadatke obrade, kao što su generiranje izvješća, isporuka obavijesti i dohvaćanje podataka iz sustava trećih strana. Propusnost sustava koji obavlja te zadatke može se jednostavno skalirati dodavanjem rukovatelja. Preostaje nam samo formirati klaster procesora i ravnomjerno raspodijeliti zadatke između njih.

Pogledajmo situacije koje se javljaju na primjeru 3 rukovatelja. Već u fazi raspodjele zadataka postavlja se pitanje pravednosti raspodjele i prelivanja rukovatelja. Round-robin raspodjela bit će zadužena za pravednost, a kako bismo izbjegli situaciju prelivanja rukovatelja, uvest ćemo ograničenje granica_prije_dohvaćanja. U prijelaznim uvjetima granica_prije_dohvaćanja spriječit će jednog rukovatelja da primi sve zadatke.

Messaging upravlja redovima i prioritetom obrade. Procesori primaju zadatke kako stignu. Zadatak se može završiti uspješno ili neuspješno:

  • messaging:ack(Tack) - poziva se ako je poruka uspješno obrađena
  • messaging:nack(Tack) - poziva u svim hitnim situacijama. Nakon što se zadatak vrati, poruka će ga proslijediti drugom rukovatelju.

Sastavni dijelovi distribuiranih aplikacija. Prvi pristup

Pretpostavimo da je došlo do složenog kvara tijekom obrade tri zadatka: procesor 1, nakon što je primio zadatak, srušio se bez vremena da bilo što prijavi točki razmjene. U tom će slučaju točka razmjene prenijeti zadatak drugom rukovatelju nakon što istekne vrijeme čekanja za potvrdu. Iz nekog razloga, rukovatelj 3 je napustio zadatak i poslao nack; kao rezultat toga, zadatak je također prebačen drugom rukovatelju koji ga je uspješno dovršio.

Preliminarni sažetak

Pokrili smo osnovne građevne blokove distribuiranih sustava i stekli osnovno razumijevanje njihove upotrebe u Erlangu/Elixiru.

Kombiniranjem osnovnih obrazaca možete izgraditi složene paradigme za rješavanje novonastalih problema.

U završnom dijelu serijala, osvrnut ćemo se na opća pitanja organizacije servisa, usmjeravanja i balansiranja, a govorit ćemo io praktičnoj strani skalabilnosti i tolerancije na pogreške sustava.

Kraj drugog dijela.

Fotografija Marius Christensen
Ilustracije pripremljene pomoću websequencediagrams.com

Izvor: www.habr.com

Dodajte komentar