Izplatīto lietojumprogrammu bloki. Pirmā pieeja

Izplatīto lietojumprogrammu bloki. Pirmā pieeja

Pagātnē raksts Mēs pārbaudījām reaktīvās arhitektūras teorētiskos pamatus. Ir pienācis laiks runāt par datu plūsmām, reaktīvo Erlang/Elixir sistēmu ieviešanas veidiem un ziņojumapmaiņas modeļiem tajās:

  • Pieprasījums-atbilde
  • Pieprasījuma sadalīta atbilde
  • Atbilde ar Pieprasījumu
  • Publicēt-abonēt
  • Apgriezta Publish-subscribe
  • Uzdevumu sadale

SOA, MSA un ziņojumapmaiņa

SOA, MSA ir sistēmu arhitektūras, kas nosaka ēku sistēmu noteikumus, savukārt ziņojumapmaiņa nodrošina primitīvus to ieviešanai.

Es nevēlos reklamēt to vai citu sistēmas arhitektūru. Es esmu par visefektīvākās un noderīgākās prakses izmantošanu konkrētam projektam un biznesam. Neatkarīgi no tā, kādu paradigmu mēs izvēlētos, labāk ir izveidot sistēmas blokus, ņemot vērā Unix-way: komponentus ar minimālu savienojamību, kas atbild par atsevišķām entītijām. API metodes veic vienkāršākās iespējamās darbības ar entītijām.

Ziņapmaiņa, kā norāda nosaukums, ir ziņojumu starpnieks. Tās galvenais mērķis ir saņemt un nosūtīt ziņojumus. Tas ir atbildīgs par informācijas nosūtīšanas saskarnēm, loģisko kanālu veidošanu informācijas pārsūtīšanai sistēmas ietvaros, maršrutēšanu un balansēšanu, kā arī kļūdu apstrādi sistēmas līmenī.
Mūsu izstrādātā ziņojumapmaiņa nemēģina konkurēt ar rabbitmq vai aizstāt to. Tās galvenās iezīmes:

  • Izplatīšana.
    Apmaiņas punktus var izveidot visos klastera mezglos, pēc iespējas tuvāk kodam, kas tos izmanto.
  • Vienkāršība.
    Koncentrējieties uz standarta koda samazināšanu un lietošanas ērtumu.
  • Labāks sniegums.
    Mēs necenšamies atkārtot rabbitmq funkcionalitāti, bet izceļam tikai arhitektūras un transporta slāni, ko maksimāli vienkārši iekļaujam OTP, samazinot izmaksas.
  • Elastīgums.
    Katrs pakalpojums var apvienot daudzas apmaiņas veidnes.
  • Izturība pēc dizaina.
  • Mērogojamība.
    Ziņojumapmaiņa pieaug līdz ar lietojumprogrammu. Pieaugot slodzei, maiņas punktus var pārvietot uz atsevišķām mašīnām.

PIEZĪME. Koda organizācijas ziņā metaprojekti ir labi piemēroti sarežģītām Erlang/Elixir sistēmām. Viss projekta kods atrodas vienā repozitorijā - jumta projektā. Tajā pašā laikā mikropakalpojumi ir maksimāli izolēti un veic vienkāršas darbības, kas ir atbildīgas par atsevišķu entītiju. Izmantojot šo pieeju, ir viegli uzturēt visas sistēmas API, ir viegli veikt izmaiņas, ir ērti rakstīt vienības un integrācijas testus.

Sistēmas komponenti mijiedarbojas tieši vai ar brokera starpniecību. No ziņojumapmaiņas viedokļa katram pakalpojumam ir vairākas darbības fāzes:

  • Pakalpojuma inicializācija.
    Šajā posmā tiek konfigurēts un palaists process un atkarības, kas izpilda pakalpojumu.
  • Maiņas punkta izveide.
    Pakalpojums var izmantot statisku apmaiņas punktu, kas norādīts mezgla konfigurācijā, vai izveidot apmaiņas punktus dinamiski.
  • Pakalpojuma reģistrācija.
    Lai pakalpojums apkalpotu pieprasījumus, tas ir jāreģistrē maiņas punktā.
  • Normāla darbība.
    Pakalpojums rada noderīgu darbu.
  • Izslēgt.
    Ir iespējami 2 izslēgšanas veidi: parasta un avārijas. Normālas darbības laikā pakalpojums tiek atvienots no maiņas punkta un apstājas. Ārkārtas situācijās ziņojumapmaiņa izpilda vienu no kļūmjpārlēces skriptiem.

Tas izskatās diezgan sarežģīti, taču kods nav tik biedējošs. Kodu piemēri ar komentāriem tiks sniegti veidņu analīzē nedaudz vēlāk.

Apmaiņas

Apmaiņas punkts ir ziņojumapmaiņas process, kas īsteno mijiedarbības loģiku ar ziņojumapmaiņas veidnes komponentiem. Visos tālāk sniegtajos piemēros komponenti mijiedarbojas, izmantojot apmaiņas punktus, kuru kombinācija veido ziņojumapmaiņu.

Ziņojumu apmaiņas modeļi (EP deputāti)

Globāli apmaiņas modeļus var iedalīt divvirzienu un vienvirziena. Pirmie nozīmē atbildi uz ienākošo ziņojumu, bet pēdējie to nedara. Klasisks divvirzienu modeļa piemērs klienta-servera arhitektūrā ir pieprasījuma-atbildes modelis. Apskatīsim veidni un tās modifikācijas.

Pieprasījums-atbilde vai RPC

RPC tiek izmantots, ja mums ir jāsaņem atbilde no cita procesa. Šis process var darboties tajā pašā mezglā vai atrodas citā kontinentā. Zemāk ir diagramma par mijiedarbību starp klientu un serveri, izmantojot ziņojumapmaiņu.

Izplatīto lietojumprogrammu bloki. Pirmā pieeja

Tā kā ziņojumapmaiņa ir pilnīgi asinhrona, klientam apmaiņa ir sadalīta 2 fāzēs:

  1. Pieprasījuma nosūtīšana

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

    maiņa ‒ unikāls maiņas punkta nosaukums
    ResponseMatchingTag ‒ vietējā etiķete atbildes apstrādei. Piemēram, ja tiek nosūtīti vairāki identiski pieprasījumi, kas pieder dažādiem lietotājiem.
    PieprasījumsDefinīcija - pieprasījuma struktūra
    HandlerProcess ‒ Apdarinātāja PID. Šis process saņems atbildi no servera.

  2. Notiek atbildes apstrāde

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

    ResponsePayload - servera atbilde.

Serverim process sastāv arī no 2 fāzēm:

  1. Apmaiņas punkta inicializācija
  2. Saņemto pieprasījumu apstrāde

Ilustrēsim šo veidni ar kodu. Pieņemsim, ka mums ir jāievieš vienkāršs pakalpojums, kas nodrošina vienu precīzu laika metodi.

Servera kods

Definēsim pakalpojuma 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{}
}).

Definēsim pakalpojuma kontrolieri failā 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.

Klienta kods

Lai nosūtītu pieprasījumu pakalpojumam, jebkurā klienta vietā varat izsaukt ziņojumapmaiņas pieprasījuma API:

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

Izkliedētā sistēmā komponentu konfigurācija var būt ļoti atšķirīga, un pieprasījuma brīdī ziņojumapmaiņa var vēl nesākties vai arī pakalpojumu kontrolieris nebūs gatavs apkalpot pieprasījumu. Tāpēc mums ir jāpārbauda ziņojumapmaiņas atbilde un jārisina kļūmes gadījums.
Pēc veiksmīgas nosūtīšanas klients saņems atbildi vai kļūdu no servisa.
Apstrādāsim abus gadījumus sadaļā 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};

Pieprasījuma sadalīta atbilde

Vislabāk ir izvairīties no lielu ziņojumu sūtīšanas. No tā ir atkarīga visas sistēmas atsaucība un stabila darbība. Ja atbilde uz vaicājumu aizņem daudz atmiņas, tad tā ir obligāti jāsadala daļās.

Izplatīto lietojumprogrammu bloki. Pirmā pieeja

Ļaujiet man sniegt jums pāris šādu gadījumu piemērus:

  • Komponenti apmainās ar bināriem datiem, piemēram, failiem. Atbildes sadalīšana mazās daļās palīdz efektīvi strādāt ar jebkura izmēra failiem un izvairīties no atmiņas pārpildes.
  • Saraksti. Piemēram, mums ir jāatlasa visi ieraksti no milzīgas datu bāzes tabulas un jāpārnes uz citu komponentu.

Es šīs atbildes saucu par lokomotīvi. Jebkurā gadījumā 1024 ziņojumi 1 MB ir labāki nekā viens 1 GB ziņojums.

Erlang klasterī mēs iegūstam papildu labumu - samazinot apmaiņas punkta un tīkla slodzi, jo atbildes tiek nekavējoties nosūtītas adresātam, apejot apmaiņas punktu.

Atbilde ar Pieprasījumu

Šī ir diezgan reta RPC modeļa modifikācija dialogu sistēmu veidošanai.

Izplatīto lietojumprogrammu bloki. Pirmā pieeja

Publicēt-abonēt (datu izplatīšanas koks)

Uz notikumiem balstītas sistēmas tos piegādā patērētājiem, tiklīdz dati ir gatavi. Tādējādi sistēmas ir vairāk pakļautas push modelim, nevis vilkšanas vai aptaujas modelim. Šī funkcija ļauj izvairīties no resursu izšķērdēšanas, pastāvīgi pieprasot un gaidot datus.
Attēlā parādīts ziņojuma izplatīšanas process patērētājiem, kas abonējuši noteiktu tēmu.

Izplatīto lietojumprogrammu bloki. Pirmā pieeja

Klasiski šī modeļa izmantošanas piemēri ir stāvokļa sadalījums: spēļu pasaule datorspēlēs, tirgus dati par biržām, noderīga informācija datu plūsmās.

Apskatīsim abonenta kodu:

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.

Avots var izsaukt funkciju, lai publicētu ziņojumu jebkurā ērtā vietā:

messaging:publish_message(Exchange, Key, Message).

maiņa - maiņas punkta nosaukums,
Taustiņš - maršrutēšanas atslēga
Ziņa - lietderīgā slodze

Apgriezta Publish-subscribe

Izplatīto lietojumprogrammu bloki. Pirmā pieeja

Paplašinot pub-sub, jūs varat iegūt reģistrēšanai ērtu modeli. Avotu un patērētāju kopums var būt pilnīgi atšķirīgs. Attēlā parādīts gadījums ar vienu patērētāju un vairākiem avotiem.

Uzdevumu sadales modelis

Gandrīz katrs projekts ietver atliktās apstrādes uzdevumus, piemēram, atskaišu ģenerēšanu, paziņojumu piegādi un datu izgūšanu no trešo pušu sistēmām. Sistēmas, kas veic šos uzdevumus, caurlaidspēju var viegli mērogot, pievienojot apstrādātājus. Mums atliek tikai izveidot procesoru kopu un vienmērīgi sadalīt uzdevumus starp tiem.

Apskatīsim situācijas, kas rodas, izmantojot 3 hendleru piemēru. Pat uzdevumu sadales posmā rodas jautājums par sadales taisnīgumu un apdarinātāju pārpilnību. Apkārtējā sadale būs atbildīga par godīgumu, un, lai izvairītos no apdarinātāju pārpildes, mēs ieviesīsim ierobežojumu. prefetch_limit. Pārejošos apstākļos prefetch_limit neļaus vienam apstrādātājam saņemt visus uzdevumus.

Ziņojumapmaiņa pārvalda rindas un apstrādes prioritāti. Procesori saņem uzdevumus, tiklīdz tie nonāk. Uzdevumu var izpildīt veiksmīgi vai neizdoties:

  • messaging:ack(Tack) - zvana, ja ziņojums ir veiksmīgi apstrādāts
  • messaging:nack(Tack) - izsaukts visās ārkārtas situācijās. Kad uzdevums ir atgriezts, ziņojumapmaiņa to nodos citam apstrādātājam.

Izplatīto lietojumprogrammu bloki. Pirmā pieeja

Pieņemsim, ka, apstrādājot trīs uzdevumus, radās sarežģīta kļūme: 1. procesors pēc uzdevuma saņemšanas avarēja, nepaspējot neko ziņot apmaiņas punktam. Šādā gadījumā apmaiņas punkts nosūtīs uzdevumu citam apstrādātājam pēc apstiprinājuma taimauta beigām. Kādu iemeslu dēļ 3. apdarinātājs pameta uzdevumu un nosūtīja nack, kā rezultātā uzdevums tika nodots citam apdarinātājam, kurš to veiksmīgi izpildīja.

Iepriekšējs kopsavilkums

Mēs esam apskatījuši sadalīto sistēmu pamatelementus un guvuši pamatzināšanas par to izmantošanu programmā Erlang/Elixir.

Apvienojot pamata modeļus, varat izveidot sarežģītas paradigmas, lai atrisinātu jaunas problēmas.

Sērijas beigu daļā apskatīsim vispārīgus pakalpojumu organizēšanas, maršrutēšanas un balansēšanas jautājumus, kā arī runāsim par sistēmu mērogojamības un kļūdu tolerances praktisko pusi.

Otrās daļas beigas.

foto Mariuss Kristensens
Ilustrācijas sagatavotas, izmantojot vietni websequencediagrams.com

Avots: www.habr.com

Pievieno komentāru