Paskirstytų programų blokai. Pirmas požiūris

Paskirstytų programų blokai. Pirmas požiūris

Paskutiniame straipsnis Išnagrinėjome reaktyviosios architektūros teorinius pagrindus. Pats metas pakalbėti apie duomenų srautus, reaktyviųjų Erlang/Elixir sistemų diegimo būdus ir pranešimų modelius jose:

  • Prašymas-atsakymas
  • Užklausa išskaidytas atsakymas
  • Atsakymas su Prašymu
  • Publikuoti-prenumeruoti
  • Apverstas Publish-Prenumeruoti
  • Užduočių paskirstymas

SOA, MSA ir pranešimų siuntimas

SOA, MSA yra sistemų architektūros, apibrėžiančios pastatų sistemų taisykles, o pranešimų siuntimas suteikia primityvų jų įgyvendinimui.

Nenoriu reklamuoti tos ar kitos sistemos architektūros. Esu už efektyviausių ir naudingiausių praktikų taikymą konkrečiam projektui ir verslui. Kad ir kokią paradigmą pasirinktume, geriau kurti sistemos blokus, atsižvelgiant į Unix-way: komponentus su minimaliu ryšiu, atsakingus už atskirus objektus. API metodai atlieka paprasčiausius įmanomus veiksmus su subjektais.

Pranešimai, kaip rodo pavadinimas, yra pranešimų tarpininkas. Jo pagrindinis tikslas yra gauti ir siųsti žinutes. Ji atsakinga už informacijos siuntimo sąsajas, loginių kanalų formavimą informacijos perdavimui sistemoje, maršruto parinkimą ir balansavimą, taip pat gedimų valdymą sistemos lygmeniu.
Mūsų kuriami pranešimai nesistengia konkuruoti ar pakeisti rabbitmq. Pagrindinės jo savybės:

  • Paskirstymas.
    Keitimosi taškai gali būti sukurti visuose klasterio mazguose, kiek įmanoma arčiau juos naudojančio kodo.
  • Paprastumas
    Sutelkite dėmesį į pagrindinio kodo sumažinimą ir naudojimo paprastumą.
  • Geresnis pasirodymas.
    Nesistengiame pakartoti rabbitmq funkcionalumo, o išryškiname tik architektūrinį ir transporto sluoksnį, kurį į OTP talpiname kuo paprasčiau, minimalizuodami išlaidas.
  • Lankstumas.
    Kiekviena paslauga gali sujungti daug mainų šablonų.
  • Atsparumas pagal dizainą.
  • Mastelio keitimas.
    Susirašinėjimas su programa plečiasi. Didėjant apkrovai, mainų taškus galite perkelti į atskiras mašinas.

Pastaba Kodo organizavimo prasme metaprojektai puikiai tinka sudėtingoms Erlang/Elixir sistemoms. Visas projekto kodas yra vienoje saugykloje – skėtiniame projekte. Tuo pačiu metu mikropaslaugos yra maksimaliai izoliuotos ir atlieka paprastas operacijas, kurios yra atsakingos už atskirą subjektą. Taikant šį metodą lengva prižiūrėti visos sistemos API, lengva atlikti pakeitimus, patogu rašyti vienetų ir integravimo testus.

Sistemos komponentai sąveikauja tiesiogiai arba per tarpininką. Žinučių siuntimo požiūriu kiekviena paslauga turi keletą etapų:

  • Paslaugos inicijavimas.
    Šiame etape sukonfigūruojamas ir paleidžiamas paslaugos vykdymo procesas ir priklausomybės.
  • Keitimo taško sukūrimas.
    Paslauga gali naudoti statinį mainų tašką, nurodytą mazgo konfigūracijoje, arba kurti mainų taškus dinamiškai.
  • Paslaugų registracija.
    Kad paslauga galėtų aptarnauti užklausas, ji turi būti užregistruota keitimo punkte.
  • Normalus veikimas.
    Paslauga atlieka naudingą darbą.
  • Darbo užbaigimas.
    Galimi 2 išjungimų tipai: įprastas ir avarinis. Įprasto veikimo metu paslauga atjungiama nuo mainų taško ir sustoja. Esant kritinėms situacijoms, pranešimų siuntimas vykdo vieną iš perkėlimo scenarijų.

Tai atrodo gana sudėtinga, tačiau kodas nėra toks baisus. Kodo pavyzdžiai su komentarais bus pateikti analizuojant šablonus šiek tiek vėliau.

Mainai

Keitimosi taškas yra pranešimų procesas, įgyvendinantis sąveikos su pranešimų šablono komponentais logiką. Visuose toliau pateiktuose pavyzdžiuose komponentai sąveikauja per mainų taškus, kurių derinys sudaro pranešimų siuntimą.

Pranešimų mainų modeliai (EP)

Pasauliniu mastu mainų modelius galima suskirstyti į dvipusius ir vienpusius. Pirmieji reiškia atsakymą į gaunamą pranešimą, antrieji – ne. Klasikinis kliento ir serverio architektūros dvipusio modelio pavyzdys yra užklausos ir atsakymo modelis. Pažvelkime į šabloną ir jo modifikacijas.

Užklausa-atsakymas arba RPC

RPC naudojamas, kai turime gauti atsakymą iš kito proceso. Šis procesas gali būti vykdomas tame pačiame mazge arba kitame žemyne. Žemiau pateikiama kliento ir serverio sąveikos per pranešimus diagrama.

Paskirstytų programų blokai. Pirmas požiūris

Kadangi pranešimų siuntimas yra visiškai asinchroninis, klientui apsikeitimas yra padalintas į 2 fazes:

  1. Pateikti užklausą

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

    mainai ‒ unikalus mainų taško pavadinimas
    ResponseMatchingTag ‒ vietinė etiketė atsakymui apdoroti. Pavyzdžiui, siunčiant kelias identiškas užklausas, priklausančias skirtingiems vartotojams.
    RequestDefinition - prašymo įstaiga
    HandlerProcess ‒ tvarkyklės PID. Šis procesas gaus atsakymą iš serverio.

  2. Apdorojamas atsakymas

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

    ResponsePayload - serverio atsakymas.

Serveryje procesas taip pat susideda iš 2 etapų:

  1. Keitimo taško inicijavimas
  2. Gautų užklausų apdorojimas

Pavaizduokime šį šabloną kodu. Tarkime, mums reikia įdiegti paprastą paslaugą, kuri suteikia vieną tikslaus laiko metodą.

Serverio kodas

Apibrėžkime paslaugos API 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{}
}).

Apibrėžkime paslaugos valdiklį 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.

Kliento kodas

Norėdami išsiųsti užklausą paslaugai, galite skambinti pranešimų siuntimo užklausos API bet kurioje kliento vietoje:

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

Paskirstytoje sistemoje komponentų konfigūracija gali būti labai skirtinga ir užklausos metu pranešimų siuntimas dar gali neprasidėti arba paslaugų valdiklis nebus pasirengęs aptarnauti užklausos. Todėl turime patikrinti pranešimų atsakymą ir tvarkyti gedimo atvejį.
Po sėkmingo siuntimo klientas gaus atsakymą arba klaidą iš tarnybos.
Abu atvejus tvarkykime hand_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};

Užklausa išskaidytas atsakymas

Geriausia vengti didelių pranešimų siuntimo. Nuo to priklauso visos sistemos reagavimas ir stabilus veikimas. Jei atsakymas į užklausą užima daug atminties, būtina jį padalyti į dalis.

Paskirstytų programų blokai. Pirmas požiūris

Pateiksiu porą tokių atvejų pavyzdžių:

  • Komponentai keičiasi dvejetainiais duomenimis, pvz., failais. Suskaidžius atsaką į mažas dalis, galėsite efektyviai dirbti su bet kokio dydžio failais ir išvengti atminties perpildymo.
  • Sąrašai. Pavyzdžiui, turime pasirinkti visus įrašus iš didžiulės duomenų bazės lentelės ir perkelti juos į kitą komponentą.

Šiuos atsakymus vadinu lokomotyvais. Bet kokiu atveju 1024 1 MB žinutės yra geriau nei viena 1 GB žinutė.

„Erlang“ klasteryje gauname papildomą naudą – sumažiname apsikeitimo taško ir tinklo apkrovą, nes atsakymai iš karto siunčiami gavėjui, apeinant apsikeitimo tašką.

Atsakymas su Prašymu

Tai gana reta RPC modelio modifikacija kuriant dialogo sistemas.

Paskirstytų programų blokai. Pirmas požiūris

Paskelbti-prenumeruoti (duomenų platinimo medis)

Įvykiais pagrįstos sistemos pateikia juos vartotojams iškart, kai tik duomenys bus paruošti. Taigi sistemos yra labiau linkusios į stumiamą modelį nei į traukimo ar apklausos modelį. Ši funkcija leidžia neeikvoti resursų, nuolat prašant ir laukiant duomenų.
Paveikslėlyje parodytas pranešimo platinimo procesas vartotojams, užsiprenumeravusiems konkrečią temą.

Paskirstytų programų blokai. Pirmas požiūris

Klasikiniai šio modelio naudojimo pavyzdžiai yra būsenos pasiskirstymas: žaidimų pasaulis kompiuteriniuose žaidimuose, rinkos duomenys apie biržas, naudinga informacija duomenų srautuose.

Pažiūrėkime į abonento 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.

Šaltinis gali iškviesti funkciją paskelbti pranešimą bet kurioje patogioje vietoje:

messaging:publish_message(Exchange, Key, Message).

mainai - keitimo vietos pavadinimas,
raktas - maršruto parinkimo raktas
Žinutė - naudingoji apkrova

Apverstas Publish-Prenumeruoti

Paskirstytų programų blokai. Pirmas požiūris

Išplėsdami „pub-sub“ galite gauti patogią rašyti modelį. Šaltinių ir vartotojų rinkinys gali būti visiškai skirtingas. Paveikslėlyje parodytas atvejis su vienu vartotoju ir keliais šaltiniais.

Užduočių paskirstymo modelis

Beveik kiekvienas projektas apima atidėtas apdorojimo užduotis, tokias kaip ataskaitų generavimas, pranešimų pateikimas ir duomenų gavimas iš trečiųjų šalių sistemų. Sistemos, atliekančios šias užduotis, pralaidumą galima lengvai padidinti pridedant tvarkykles. Mums belieka suformuoti procesorių klasterį ir tolygiai paskirstyti užduotis tarp jų.

Pažvelkime į susidariusias situacijas naudodamiesi 3 tvarkytojų pavyzdžiu. Net užduočių paskirstymo etape iškyla paskirstymo teisingumo ir tvarkytojų perpildymo klausimas. Paskirstymas ratu bus atsakingas už sąžiningumą, o norėdami išvengti tvarkytojų perpildymo, įvesime apribojimą prefetch_limit. Laikinomis sąlygomis prefetch_limit neleis vienam tvarkytojui gauti visų užduočių.

Pranešimų siuntimas valdo eiles ir apdorojimo prioritetą. Procesoriai gauna užduotis, kai tik ateina. Užduotis gali būti sėkmingai atlikta arba nepavyksta:

  • messaging:ack(Tack) - skambinama, jei pranešimas sėkmingai apdorotas
  • messaging:nack(Tack) – skambinama visose avarinėse situacijose. Kai užduotis bus grąžinta, susirašinėjimo žinutėmis ji bus perduota kitam tvarkytojui.

Paskirstytų programų blokai. Pirmas požiūris

Tarkime, apdorojant tris užduotis įvyko sudėtingas gedimas: 1 procesorius, gavęs užduotį, sugedo, nespėjęs nieko pranešti mainų taškui. Tokiu atveju mainų taškas perduos užduotį kitam tvarkytojui pasibaigus patvirtinimo laikui. Dėl tam tikrų priežasčių 3 prižiūrėtojas atsisakė užduoties ir atsiuntė nack; dėl to užduotis taip pat buvo perduota kitam tvarkytojui, kuris sėkmingai ją atliko.

Preliminari santrauka

Apžvelgėme pagrindinius paskirstytų sistemų blokus ir įgijome pagrindinį supratimą apie jų naudojimą Erlang/Elixir.

Derindami pagrindinius modelius, galite sukurti sudėtingas paradigmas, kad išspręstumėte kylančias problemas.

Paskutinėje serijos dalyje apžvelgsime bendrus paslaugų organizavimo, maršruto parinkimo ir balansavimo klausimus, taip pat pakalbėsime apie praktinę sistemų mastelio ir gedimų tolerancijos pusę.

Antros dalies pabaiga.

nuotrauka Marius Christensenas
Iliustracijos parengtos naudojant websequencediagrams.com

Šaltinis: www.habr.com

Добавить комментарий