En la lasta
- Peto-respondo
- Peto-Fraca Respondo
- Respondo kun Peto
- Eldoni-aboni
- Invertita Publiki-aboni
- Distribuo de taskoj
SOA, MSA kaj Mesaĝado
SOA, MSA estas sistemaj arkitekturoj kiuj difinas la regulojn por konstruado de sistemoj, dum mesaĝado disponigas primitivulojn por ilia efektivigo.
Mi ne volas promocii tiun aŭ alian sisteman arkitekturon. Mi estas por uzi la plej efikajn kaj utilajn praktikojn por specifa projekto kaj komerco. Kian ajn paradigmon ni elektas, estas pli bone krei sistemajn blokojn kun okulo sur la Unikso-maniero: komponantoj kun minimuma konektebleco, respondecaj pri individuaj estaĵoj. API-metodoj plenumas la plej simplajn eblajn agojn kun entoj.
Mesaĝado estas, kiel la nomo sugestas, mesaĝmakleristo. Ĝia ĉefa celo estas ricevi kaj sendi mesaĝojn. Ĝi respondecas pri la interfacoj por sendado de informoj, la formado de logikaj kanaloj por elsendado de informoj ene de la sistemo, vojigo kaj balancado, same kiel mistraktado sur la sistemnivelo.
La mesaĝado, kiun ni disvolvas, ne provas konkuri aŭ anstataŭigi rabbitmq. Ĝiaj ĉefaj trajtoj:
- Distribuado.
Interŝanĝpunktoj povas esti kreitaj sur ĉiuj aretnodoj, kiel eble plej proksime al la kodo kiu uzas ilin. - Simpleco.
Fokusu minimumigi kodon kaj facilecon de uzo. - Pli bona agado.
Ni ne provas ripeti la funkciecon de rabbitmq, sed reliefigi nur la arkitekturan kaj transportan tavolon, kiun ni eniras en la OTP kiel eble plej simple, minimumigante kostojn. - Fleksebleco.
Ĉiu servo povas kombini multajn interŝanĝajn ŝablonojn. - Eltenemo laŭ dezajno.
- Skalebleco.
Mesaĝado kreskas kun la aplikaĵo. Dum la ŝarĝo pliiĝas, vi povas movi la interŝanĝpunktojn al individuaj maŝinoj.
Komento. Koncerne al koda organizo, meta-projektoj taŭgas por kompleksaj Erlang/Elixir-sistemoj. Ĉiu projektkodo troviĝas en unu deponejo - tegmenta projekto. Samtempe, mikroservoj estas maksimume izolitaj kaj plenumas simplajn operaciojn, kiuj respondecas pri aparta ento. Kun ĉi tiu aliro, estas facile konservi la API de la tuta sistemo, estas facile fari ŝanĝojn, estas oportune skribi unuajn kaj integrigajn testojn.
La sistemaj komponantoj interagas rekte aŭ per makleristo. De mesaĝa perspektivo, ĉiu servo havas plurajn vivfazojn:
- Serva inicialigo.
En ĉi tiu etapo, la procezo ekzekutanta la servon kaj ĝiajn dependecojn estas agordita kaj lanĉita. - Kreante interŝanĝpunkton.
La servo povas uzi senmovan interŝanĝpunkton specifitan en la noda agordo, aŭ krei interŝanĝpunktojn dinamike. - Serva registriĝo.
Por ke la servo servas petojn, ĝi devas esti registrita ĉe la interŝanĝa punkto. - Normala funkciado.
La servo produktas utilan laboron. - Malŝalto.
Estas 2 specoj de haltigo eblaj: normala kaj kriza. Dum normala operacio, la servo estas malkonektita de la interŝanĝpunkto kaj haltas. En krizaj situacioj, mesaĝado efektivigas unu el la malsukcesaj skriptoj.
Ĝi aspektas sufiĉe komplika, sed la kodo ne estas tiom timiga. Kodekzemploj kun komentoj estos donitaj en la analizo de ŝablonoj iom poste.
interŝanĝoj
Interŝanĝpunkto estas mesaĝa procezo kiu efektivigas la logikon de interago kun komponantoj ene de la mesaĝa ŝablono. En ĉiuj ekzemploj prezentitaj sube, la komponantoj interagas per interŝanĝpunktoj, kies kombinaĵo formas mesaĝadon.
Mesaĝaj interŝanĝaj ŝablonoj (MEPoj)
Tutmonde, interŝanĝaj ŝablonoj povas esti dividitaj en dudirektan kaj unudirektan. La unuaj implicas respondon al envenanta mesaĝo, la lastaj ne faras. Klasika ekzemplo de dudirekta ŝablono en klient-servila arkitekturo estas la Peto-responda ŝablono. Ni rigardu la ŝablonon kaj ĝiajn modifojn.
Peto-respondo aŭ RPC
RPC estas uzata kiam ni bezonas ricevi respondon de alia procezo. Ĉi tiu procezo povas funkcii sur la sama nodo aŭ situanta sur malsama kontinento. Malsupre estas diagramo de la interago inter kliento kaj servilo per mesaĝado.
Ĉar mesaĝado estas tute nesinkrona, por la kliento la interŝanĝo estas dividita en 2 fazojn:
-
Submetu peton
messaging:request(Exchange, ResponseMatchingTag, RequestDefinition, HandlerProcess).
interŝanĝo ‒ unika nomo de la interŝanĝopunkto
ResponseMatchingTag ‒ loka etikedo por prilabori la respondon. Ekzemple, en la kazo de sendado de pluraj identaj petoj apartenantaj al malsamaj uzantoj.
PetoDifino - peto korpo
HandlerProcess ‒ PID de la prizorganto. Ĉi tiu procezo ricevos respondon de la servilo. -
Prilaborado de la respondo
handle_info(#'$msg'{exchange = EXCHANGE, tag = ResponseMatchingTag,message = ResponsePayload}, State)
ResponsePayload - servila respondo.
Por la servilo, la procezo ankaŭ konsistas el 2 fazoj:
- Komencante la interŝanĝpunkton
- Prilaborado de ricevitaj petoj
Ni ilustru ĉi tiun ŝablonon per kodo. Ni diru, ke ni devas efektivigi simplan servon, kiu provizas ununuran ĝustan tempon.
Servila kodo
Ni difinu la servon API en 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{}
}).
Ni difinu la servoregilon en 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.
Klientokodo
Por sendi peton al la servo, vi povas voki la mesaĝan peton API ie ajn en la kliento:
case messaging:request(?EXCHANGE, tag, #time_req{opts = #{}}, self()) of
ok -> ok;
_ -> %% repeat or fail logic
end
En distribuita sistemo, la agordo de komponantoj povas esti tre malsama kaj en la momento de la peto, mesaĝado eble ankoraŭ ne komenciĝas, aŭ la serva regilo ne estos preta servi la peton. Tial ni devas kontroli la mesaĝan respondon kaj trakti la fiaskan kazon.
Post sukcesa sendo, la kliento ricevos respondon aŭ eraron de la servo.
Ni traktu ambaŭ kazojn en 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};
Peto-Fraca Respondo
Plej bone eviti sendi grandegajn mesaĝojn. De tio dependas la respondeco kaj stabila funkciado de la tuta sistemo. Se la respondo al demando okupas multe da memoro, tiam dividi ĝin en partojn estas deviga.
Mi donu al vi kelkajn ekzemplojn de tiaj kazoj:
- La komponantoj interŝanĝas binarajn datumojn, kiel dosierojn. Rompi la respondon en malgrandajn partojn helpas vin labori efike kun dosieroj de ajna grandeco kaj eviti memorajn superfluojn.
- Listoj. Ekzemple, ni devas elekti ĉiujn registrojn el grandega tabelo en la datumbazo kaj transdoni ilin al alia komponanto.
Mi nomas ĉi tiujn respondojn lokomotivo. Ĉiukaze, 1024 mesaĝoj de 1 MB estas pli bonaj ol ununura mesaĝo de 1 GB.
En la Erlang-areo, ni ricevas plian avantaĝon - reduktante la ŝarĝon sur la interŝanĝpunkto kaj la reto, ĉar respondoj estas tuj senditaj al la ricevanto, preterirante la interŝanĝpunkton.
Respondo kun Peto
Ĉi tio estas sufiĉe malofta modifo de la RPC-ŝablono por konstrui dialogsistemojn.
Publiki-aboni (datuma distribuarbo)
Event-movitaj sistemoj liveras ilin al konsumantoj tuj kiam la datumoj estas pretaj. Tiel, sistemoj estas pli emaj al puŝmodelo ol al tiro aŭ balotmodelo. Ĉi tiu funkcio ebligas al vi eviti malŝpari rimedojn konstante petante kaj atendante datumojn.
La figuro montras la procezon de distribuado de mesaĝo al konsumantoj abonitaj al specifa temo.
Klasikaj ekzemploj de uzado de ĉi tiu ŝablono estas la distribuo de ŝtato: la ludmondo en komputilaj ludoj, merkataj datumoj pri interŝanĝoj, utilaj informoj en datumfluoj.
Ni rigardu la abonan kodon:
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.
La fonto povas voki la funkcion por publikigi mesaĝon en iu ajn oportuna loko:
messaging:publish_message(Exchange, Key, Message).
interŝanĝo - nomo de la interŝanĝpunkto,
ŝlosilo - vojŝlosilo
Mesaĝo - utila ŝarĝo
Invertita Publiki-aboni
Pligrandigante pub-sub, vi povas akiri ŝablonon konvenan por registri. La aro de fontoj kaj konsumantoj povas esti tute malsama. La figuro montras kazon kun unu konsumanto kaj multoblaj fontoj.
Taskodistribua ŝablono
Preskaŭ ĉiu projekto implikas prokrastitajn taskojn, kiel ekzemple generado de raportoj, liverado de sciigoj kaj reakiro de datumoj de triaj sistemoj. La trairo de la sistemo plenumanta ĉi tiujn taskojn povas esti facile skalita aldonante prizorgantojn. Restas al ni nur formi aron da procesoroj kaj egale distribui taskojn inter ili.
Ni rigardu la situaciojn kiuj ekestas uzante la ekzemplon de 3 prizorgantoj. Eĉ en la stadio de distribuo de taskoj, aperas la demando pri justeco de distribuo kaj superfluo de prizorgantoj. Ĉirkaŭ-subskribolista distribuo respondecos pri justeco, kaj por eviti situacion de superfluo de prizorgantoj, ni enkondukos limigon. prefetch_limo. En pasemaj kondiĉoj prefetch_limo malhelpos unu prizorganton ricevi ĉiujn taskojn.
Mesaĝado administras atendovicojn kaj prilaboran prioritaton. Procesoroj ricevas taskojn kiam ili alvenas. La tasko povas plenumi sukcese aŭ malsukcesi:
messaging:ack(Tack)
- vokita se la mesaĝo estas sukcese procesitamessaging:nack(Tack)
- vokis en ĉiuj krizaj situacioj. Post kiam la tasko estas resendita, mesaĝado transdonos ĝin al alia prizorganto.
Supozu ke kompleksa fiasko okazis dum prilaborado de tri taskoj: procesoro 1, post ricevado de la tasko, kraŝis sen havi tempon raporti ion ajn al la interŝanĝpunkto. En ĉi tiu kazo, la interŝanĝpunkto translokigos la taskon al alia prizorganto post kiam la ack-tempo eksvalidiĝis. Ial, prizorganto 3 prirezignis la taskon kaj sendis nakon; kiel rezulto, la tasko ankaŭ estis transdonita al alia prizorganto kiu sukcese kompletigis ĝin.
Prepara resumo
Ni kovris la bazajn konstrubriketojn de distribuitaj sistemoj kaj akiris bazan komprenon pri ilia uzo en Erlang/Elixir.
Kombinante bazajn ŝablonojn, vi povas konstrui kompleksajn paradigmojn por solvi emerĝantajn problemojn.
En la fina parto de la serio, ni rigardos ĝeneralajn aferojn pri organizado de servoj, vojigo kaj ekvilibro, kaj ankaŭ parolos pri la praktika flanko de skaleblo kaj misfunkciado de sistemoj.
Fino de la dua parto.
Foto
Ilustraĵoj preparitaj uzante websequencediagrams.com
fonto: www.habr.com