Pagātnē
- 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.
Tā kā ziņojumapmaiņa ir pilnīgi asinhrona, klientam apmaiņa ir sadalīta 2 fāzēs:
-
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. -
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:
- Apmaiņas punkta inicializācija
- 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.
Ļ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.
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.
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
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ātsmessaging:nack(Tack)
- izsaukts visās ārkārtas situācijās. Kad uzdevums ir atgriezts, ziņojumapmaiņa to nodos citam apstrādātājam.
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
Ilustrācijas sagatavotas, izmantojot vietni websequencediagrams.com
Avots: www.habr.com