Mga bloke ng gusali ng mga ipinamamahaging aplikasyon. Unang diskarte

Mga bloke ng gusali ng mga ipinamamahaging aplikasyon. Unang diskarte

Sa huli Artikulo Sinuri namin ang teoretikal na pundasyon ng reaktibong arkitektura. Oras na para pag-usapan ang tungkol sa mga daloy ng data, mga paraan para ipatupad ang mga reaktibong Erlang/Elixir system at mga pattern ng pagmemensahe sa mga ito:

  • Kahilingan-tugon
  • Request-Chunked na Tugon
  • Tugon na may Kahilingan
  • I-publish-subscribe
  • Baliktad na Publish-subscribe
  • Pamamahagi ng gawain

SOA, MSA at Pagmemensahe

Ang SOA, MSA ay mga arkitektura ng system na tumutukoy sa mga patakaran para sa pagbuo ng mga sistema, habang ang pagmemensahe ay nagbibigay ng mga primitive para sa kanilang pagpapatupad.

Hindi ko gustong i-promote ito o ang arkitektura ng system na iyon. Ako ay para sa paggamit ng pinakaepektibo at kapaki-pakinabang na mga kasanayan para sa isang partikular na proyekto at negosyo. Anuman ang paradigm na pipiliin namin, mas mahusay na lumikha ng mga bloke ng system na may mata sa Unix-way: mga bahagi na may kaunting koneksyon, na responsable para sa mga indibidwal na entity. Ginagawa ng mga pamamaraan ng API ang pinakasimpleng posibleng pagkilos sa mga entity.

Ang pagmemensahe ay, gaya ng ipinahihiwatig ng pangalan, isang broker ng mensahe. Ang pangunahing layunin nito ay tumanggap at magpadala ng mga mensahe. Ito ay responsable para sa mga interface para sa pagpapadala ng impormasyon, ang pagbuo ng mga lohikal na channel para sa pagpapadala ng impormasyon sa loob ng system, pagruruta at pagbabalanse, pati na rin ang paghawak ng fault sa antas ng system.
Ang pagmemensahe na ginagawa namin ay hindi sinusubukang makipagkumpitensya o palitan ang rabbitmq. Mga pangunahing tampok nito:

  • Pamamahagi.
    Maaaring gawin ang mga exchange point sa lahat ng cluster node, na mas malapit hangga't maaari sa code na gumagamit sa kanila.
  • Pagiging simple.
    Tumutok sa pagliit ng boilerplate code at kadalian ng paggamit.
  • Mas magandang pagtanghal.
    Hindi namin sinusubukang ulitin ang functionality ng rabbitmq, ngunit i-highlight lamang ang layer ng arkitektura at transportasyon, na nababagay namin sa OTP nang simple hangga't maaari, na pinapaliit ang mga gastos.
  • Kakayahang umangkop.
    Ang bawat serbisyo ay maaaring pagsamahin ang maraming mga template ng palitan.
  • Katatagan ayon sa disenyo.
  • Scalability.
    Lumalaki ang pagmemensahe kasama ng application. Habang tumataas ang load, maaari mong ilipat ang mga exchange point sa mga indibidwal na makina.

Pansin. Sa mga tuntunin ng organisasyon ng code, ang mga meta-proyekto ay angkop na angkop para sa mga kumplikadong sistema ng Erlang/Elixir. Ang lahat ng code ng proyekto ay matatagpuan sa isang imbakan - isang payong proyekto. Kasabay nito, ang mga microservice ay lubos na nakahiwalay at nagsasagawa ng mga simpleng operasyon na responsable para sa isang hiwalay na entity. Sa diskarteng ito, madaling mapanatili ang API ng buong system, madaling gumawa ng mga pagbabago, maginhawang magsulat ng mga pagsubok sa unit at integration.

Ang mga bahagi ng system ay direktang nakikipag-ugnayan o sa pamamagitan ng isang broker. Mula sa pananaw sa pagmemensahe, ang bawat serbisyo ay may ilang yugto ng buhay:

  • Pagsisimula ng serbisyo.
    Sa yugtong ito, ang proseso at mga dependency na nagsasagawa ng serbisyo ay na-configure at inilunsad.
  • Paglikha ng isang exchange point.
    Ang serbisyo ay maaaring gumamit ng isang static na exchange point na tinukoy sa configuration ng node, o lumikha ng mga exchange point nang pabago-bago.
  • Pagpaparehistro ng serbisyo.
    Upang maihatid ng serbisyo ang mga kahilingan, dapat itong nakarehistro sa exchange point.
  • Normal na gumagana.
    Ang serbisyo ay gumagawa ng kapaki-pakinabang na gawain.
  • Pagsara.
    Mayroong 2 uri ng pag-shutdown na posible: normal at emergency. Sa normal na operasyon, ang serbisyo ay hindi nakakonekta sa exchange point at humihinto. Sa mga emergency na sitwasyon, ang pagmemensahe ay nagpapatupad ng isa sa mga failover na script.

Mukhang medyo kumplikado, ngunit ang code ay hindi lahat na nakakatakot. Ang mga halimbawa ng code na may mga komento ay ibibigay sa pagsusuri ng mga template mamaya.

Palitan

Ang exchange point ay isang proseso ng pagmemensahe na nagpapatupad ng lohika ng pakikipag-ugnayan sa mga bahagi sa loob ng template ng pagmemensahe. Sa lahat ng mga halimbawang ipinakita sa ibaba, ang mga bahagi ay nakikipag-ugnayan sa pamamagitan ng mga exchange point, na ang kumbinasyon ay bumubuo ng pagmemensahe.

Mga pattern ng palitan ng mensahe (MEPs)

Sa buong mundo, ang mga pattern ng palitan ay maaaring hatiin sa two-way at one-way. Ang una ay nagpapahiwatig ng tugon sa isang papasok na mensahe, ang huli ay hindi. Ang isang klasikong halimbawa ng isang two-way na pattern sa arkitektura ng client-server ay ang Request-response pattern. Tingnan natin ang template at ang mga pagbabago nito.

Kahilingan-tugon o RPC

Ginagamit ang RPC kapag kailangan nating makatanggap ng tugon mula sa ibang proseso. Ang prosesong ito ay maaaring tumatakbo sa parehong node o matatagpuan sa ibang kontinente. Nasa ibaba ang isang diagram ng pakikipag-ugnayan sa pagitan ng kliyente at server sa pamamagitan ng pagmemensahe.

Mga bloke ng gusali ng mga ipinamamahaging aplikasyon. Unang diskarte

Dahil ang pagmemensahe ay ganap na asynchronous, para sa kliyente ang palitan ay nahahati sa 2 yugto:

  1. Nagpapadala ng kahilingan

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

    Palitan β€’ natatanging pangalan ng exchange point
    ResponseMatchingTag β€’ lokal na label para sa pagproseso ng tugon. Halimbawa, sa kaso ng pagpapadala ng ilang magkakaparehong kahilingan na pagmamay-ari ng iba't ibang user.
    RequestDefinition - humiling ng katawan
    HandlerProcess β€’ PID ng handler. Ang prosesong ito ay makakatanggap ng tugon mula sa server.

  2. Pinoproseso ang tugon

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

    ResponsePayload - tugon ng server.

Para sa server, ang proseso ay binubuo din ng 2 phase:

  1. Sinisimulan ang exchange point
  2. Pagproseso ng mga natanggap na kahilingan

Ilarawan natin ang template na ito gamit ang code. Sabihin nating kailangan nating magpatupad ng isang simpleng serbisyo na nagbibigay ng isang eksaktong paraan ng oras.

Code ng server

Tukuyin natin ang API ng serbisyo sa 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{}
}).

Tukuyin natin ang controller ng serbisyo sa 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.

Code ng kliyente

Upang magpadala ng kahilingan sa serbisyo, maaari mong tawagan ang API ng kahilingan sa pagmemensahe saanman sa kliyente:

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

Sa isang distributed system, ang configuration ng mga bahagi ay maaaring ibang-iba at sa oras ng kahilingan, ang pagmemensahe ay maaaring hindi pa magsimula, o ang service controller ay hindi handang i-serve ang kahilingan. Samakatuwid, kailangan nating suriin ang tugon sa pagmemensahe at pangasiwaan ang kaso ng pagkabigo.
Pagkatapos ng matagumpay na pagpapadala, ang kliyente ay makakatanggap ng tugon o error mula sa serbisyo.
Pangasiwaan natin ang parehong mga kaso sa 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-Chunked na Tugon

Pinakamainam na iwasan ang pagpapadala ng malalaking mensahe. Ang kakayahang tumugon at matatag na operasyon ng buong sistema ay nakasalalay dito. Kung ang tugon sa isang query ay tumatagal ng maraming memorya, kung gayon ang paghahati nito sa mga bahagi ay sapilitan.

Mga bloke ng gusali ng mga ipinamamahaging aplikasyon. Unang diskarte

Hayaan akong magbigay sa iyo ng ilang halimbawa ng mga ganitong kaso:

  • Ang mga bahagi ay nagpapalitan ng binary data, tulad ng mga file. Ang paghahati-hati ng tugon sa maliliit na bahagi ay nakakatulong sa iyong gumana nang mahusay sa mga file sa anumang laki at maiwasan ang mga overflow ng memory.
  • Mga listahan. Halimbawa, kailangan nating piliin ang lahat ng mga tala mula sa isang malaking talahanayan sa database at ilipat ang mga ito sa isa pang bahagi.

Tinatawag ko itong mga tugon na lokomotibo. Sa anumang kaso, ang 1024 na mensahe ng 1 MB ay mas mahusay kaysa sa isang mensahe ng 1 GB.

Sa Erlang cluster, nakakakuha kami ng karagdagang benepisyo - binabawasan ang load sa exchange point at sa network, dahil ang mga tugon ay agad na ipinadala sa tatanggap, na lumalampas sa exchange point.

Tugon na may Kahilingan

Ito ay isang medyo bihirang pagbabago ng pattern ng RPC para sa pagbuo ng mga dialog system.

Mga bloke ng gusali ng mga ipinamamahaging aplikasyon. Unang diskarte

Publish-subscribe (data distribution tree)

Ang mga system na hinimok ng kaganapan ay naghahatid sa kanila sa mga consumer sa sandaling handa na ang data. Kaya, ang mga system ay mas madaling kapitan ng push model kaysa sa pull o poll model. Ang tampok na ito ay nagbibigay-daan sa iyo upang maiwasan ang pag-aaksaya ng mga mapagkukunan sa pamamagitan ng patuloy na paghiling at paghihintay ng data.
Ipinapakita ng figure ang proseso ng pamamahagi ng mensahe sa mga consumer na naka-subscribe sa isang partikular na paksa.

Mga bloke ng gusali ng mga ipinamamahaging aplikasyon. Unang diskarte

Ang mga klasikong halimbawa ng paggamit ng pattern na ito ay ang pamamahagi ng estado: ang mundo ng laro sa mga laro sa computer, data ng merkado sa mga palitan, kapaki-pakinabang na impormasyon sa mga feed ng data.

Tingnan natin ang subscriber code:

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.

Maaaring tawagan ng pinagmulan ang function upang mag-publish ng mensahe sa anumang maginhawang lugar:

messaging:publish_message(Exchange, Key, Message).

Palitan - pangalan ng exchange point,
Key - routing key
mensahe - kargamento

Baliktad na Publish-subscribe

Mga bloke ng gusali ng mga ipinamamahaging aplikasyon. Unang diskarte

Sa pamamagitan ng pagpapalawak ng pub-sub, makakakuha ka ng pattern na maginhawa para sa pag-log. Ang hanay ng mga mapagkukunan at mga mamimili ay maaaring ganap na naiiba. Ipinapakita ng figure ang isang kaso na may isang consumer at maraming source.

Pattern ng pamamahagi ng gawain

Halos bawat proyekto ay nagsasangkot ng mga ipinagpaliban na gawain sa pagproseso, tulad ng pagbuo ng mga ulat, paghahatid ng mga abiso, at pagkuha ng data mula sa mga third-party na system. Ang throughput ng system na gumaganap sa mga gawaing ito ay madaling ma-scale sa pamamagitan ng pagdaragdag ng mga humahawak. Ang natitira na lang sa amin ay bumuo ng isang kumpol ng mga processor at pantay na ipamahagi ang mga gawain sa pagitan nila.

Tingnan natin ang mga sitwasyon na lumitaw gamit ang halimbawa ng 3 handler. Kahit na sa yugto ng pamamahagi ng gawain, ang tanong ng pagiging patas ng pamamahagi at pag-apaw ng mga humahawak ay lumitaw. Ang pamamahagi ng round-robin ay magiging responsable para sa pagiging patas, at upang maiwasan ang isang sitwasyon ng pag-apaw ng mga humahawak, maglalagay kami ng isang paghihigpit prefetch_limit. Sa mga lumilipas na kondisyon prefetch_limit ay pipigil sa isang handler mula sa pagtanggap ng lahat ng mga gawain.

Pinamamahalaan ng pagmemensahe ang mga pila at priyoridad sa pagproseso. Tumatanggap ang mga processor ng mga gawain pagdating nila. Maaaring matagumpay o mabigo ang gawain:

  • messaging:ack(Tack) - tinawag kung matagumpay na naproseso ang mensahe
  • messaging:nack(Tack) - tinawag sa lahat ng sitwasyong pang-emergency. Kapag naibalik na ang gawain, ipapasa ito ng pagmemensahe sa ibang handler.

Mga bloke ng gusali ng mga ipinamamahaging aplikasyon. Unang diskarte

Ipagpalagay na isang kumplikadong pagkabigo ang naganap habang pinoproseso ang tatlong gawain: ang processor 1, pagkatapos matanggap ang gawain, ay nag-crash nang walang oras upang mag-ulat ng anuman sa exchange point. Sa kasong ito, ililipat ng exchange point ang gawain sa isa pang handler pagkatapos mag-expire ang ack timeout. Para sa ilang kadahilanan, inabandona ng handler 3 ang gawain at nagpadala ng nack; bilang isang resulta, ang gawain ay inilipat din sa isa pang handler na matagumpay na natapos ito.

Paunang buod

Sinaklaw namin ang mga pangunahing bloke ng pagbuo ng mga distributed system at nakakuha ng pangunahing pag-unawa sa paggamit ng mga ito sa Erlang/Elixir.

Sa pamamagitan ng pagsasama-sama ng mga pangunahing pattern, maaari kang bumuo ng mga kumplikadong paradigm upang malutas ang mga umuusbong na problema.

Sa huling bahagi ng serye, titingnan natin ang mga pangkalahatang isyu ng pag-aayos ng mga serbisyo, pagruruta at pagbabalanse, at pag-uusapan din ang praktikal na bahagi ng scalability at fault tolerance ng mga system.

Katapusan ng ikalawang bahagi.

Larawan Marius Christensen
Mga paglalarawang inihanda gamit ang websequencediagrams.com

Pinagmulan: www.habr.com

Magdagdag ng komento