Viimeisessä
- Pyyntö-vastaus
- Request-Cunked Response
- Vastaa Pyynnöllä
- Julkaise-tilaa
- Käänteinen Publish-subscribe
- Tehtävien jako
SOA, MSA ja Messaging
SOA, MSA ovat järjestelmäarkkitehtuureja, jotka määrittävät säännöt rakennusjärjestelmille, kun taas viestintä tarjoaa primitiivit niiden toteuttamiseen.
En halua mainostaa tätä tai toista järjestelmäarkkitehtuuria. Haluan käyttää tehokkaimpia ja hyödyllisimpiä käytäntöjä tietyssä projektissa ja liiketoiminnassa. Minkä paradigman valitsemmekin, on parempi luoda järjestelmälohkoja Unix-tietä silmällä pitäen: komponentteja, joilla on minimaalinen liitettävyys ja jotka vastaavat yksittäisistä kokonaisuuksista. API-menetelmät suorittavat yksinkertaisimpia mahdollisia toimintoja entiteettien kanssa.
Viestit on, kuten nimestä voi päätellä, viestien välittäjä. Sen päätarkoitus on vastaanottaa ja lähettää viestejä. Se vastaa tiedon lähetysrajapinnoista, loogisten kanavien muodostamisesta tiedon siirtoon järjestelmän sisällä, reitityksestä ja tasapainotuksesta sekä järjestelmätason viankäsittelystä.
Kehitämämme viestintä ei yritä kilpailla rabbitmq:n kanssa tai korvata sitä. Sen pääominaisuudet:
- Jakelu.
Vaihtopisteitä voidaan luoda kaikkiin klusterin solmuihin, mahdollisimman lähelle niitä käyttävää koodia. - Yksinkertaisuus.
Keskity mallikoodin minimoimiseen ja helppokäyttöisyyteen. - Parempi suorituskyky.
Emme yritä toistaa rabbitmq:n toimivuutta, vaan korostamme vain arkkitehtuuri- ja kuljetuskerrosta, jonka sovitamme OTP:hen mahdollisimman yksinkertaisesti, minimoiden kustannukset. - Joustavuus.
Jokainen palvelu voi yhdistää useita vaihtomalleja. - Suunnittelun joustavuus.
- Skaalautuvuus.
Viestit kasvavat sovelluksen mukana. Kuorman kasvaessa voit siirtää vaihtopisteitä yksittäisiin koneisiin.
Huom. Koodin organisoinnin kannalta metaprojektit sopivat hyvin monimutkaisiin Erlang/Elixir-järjestelmiin. Kaikki projektikoodit sijaitsevat yhdessä arkistossa - sateenvarjoprojektissa. Samalla mikropalvelut ovat mahdollisimman eristettyjä ja suorittavat yksinkertaisia toimintoja, jotka vastaavat erillisestä kokonaisuudesta. Tällä lähestymistavalla on helppo ylläpitää koko järjestelmän API:ta, on helppo tehdä muutoksia, on kätevää kirjoittaa yksikkö- ja integraatiotestejä.
Järjestelmän osat ovat vuorovaikutuksessa suoraan tai välittäjän kautta. Viestinvälityksen näkökulmasta jokaisella palvelulla on useita elämänvaiheita:
- Palvelun alustus.
Tässä vaiheessa palvelua suorittava prosessi ja riippuvuudet määritetään ja käynnistetään. - Vaihtopisteen luominen.
Palvelu voi käyttää solmukonfiguraatiossa määritettyä staattista vaihtopistettä tai luoda vaihtopisteitä dynaamisesti. - Palvelun rekisteröinti.
Jotta palvelu voi palvella pyyntöjä, se on rekisteröitävä vaihtopisteessä. - Normaali toiminta.
Palvelu tuottaa hyödyllistä työtä. - Sammuttaa.
On 2 mahdollista sammutustyyppiä: normaali ja hätäpysäytys. Normaalin toiminnan aikana palvelu katkeaa vaihtopisteestä ja pysähtyy. Hätätilanteissa viestintä suorittaa yhden vikasietokomentosarjasta.
Se näyttää melko monimutkaiselta, mutta koodi ei ole niin pelottava. Koodiesimerkkejä kommentteineen annetaan mallien analysoinnissa hieman myöhemmin.
Vaihto
Vaihtopiste on viestintäprosessi, joka toteuttaa vuorovaikutuksen logiikan viestimallin komponenttien kanssa. Kaikissa alla esitetyissä esimerkeissä komponentit ovat vuorovaikutuksessa vaihtopisteiden kautta, joiden yhdistelmä muodostaa viestinnän.
Viestinvaihtomallit (MEP)
Globaalisti vaihtomallit voidaan jakaa kaksisuuntaisiin ja yksisuuntaisiin. Edellinen tarkoittaa vastausta saapuvaan viestiin, jälkimmäiset eivät. Klassinen esimerkki kaksisuuntaisesta mallista asiakas-palvelin-arkkitehtuurissa on Request-response -malli. Katsotaanpa mallia ja sen muutoksia.
Pyyntö-vastaus tai RPC
RPC:tä käytetään, kun meidän on saatava vastaus toiselta prosessilta. Tämä prosessi voi olla käynnissä samassa solmussa tai eri mantereella. Alla on kaavio asiakkaan ja palvelimen välisestä vuorovaikutuksesta viestien kautta.
Koska viestintä on täysin asynkronista, asiakkaalle vaihto on jaettu kahteen vaiheeseen:
-
Pyynnön lähettäminen
messaging:request(Exchange, ResponseMatchingTag, RequestDefinition, HandlerProcess).
vaihto ‒ vaihtopisteen yksilöllinen nimi
ResponseMatchingTag ‒ paikallinen etiketti vastauksen käsittelyä varten. Esimerkiksi, jos lähetetään useita identtisiä pyyntöjä, jotka kuuluvat eri käyttäjille.
RequestDefinition - pyynnön elin
Käsittelijäprosessi ‒ Käsittelijän PID. Tämä prosessi saa vastauksen palvelimelta. -
Vastausta käsitellään
handle_info(#'$msg'{exchange = EXCHANGE, tag = ResponseMatchingTag,message = ResponsePayload}, State)
ResponsePayload - palvelimen vastaus.
Palvelimen osalta prosessi koostuu myös kahdesta vaiheesta:
- Vaihtopisteen alustaminen
- Vastaanotettujen pyyntöjen käsittely
Havainnollistetaan tämä malli koodilla. Oletetaan, että meidän on otettava käyttöön yksinkertainen palvelu, joka tarjoaa yhden tarkan aikamenetelmän.
Palvelimen koodi
Määritetään palvelun sovellusliittymä api.hrl:ssä:
%% =====================================================
%% 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{}
}).
Määritetään palvelun ohjain tiedostossa 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.
Asiakaskoodi
Pyynnön lähettämiseksi palveluun voit soittaa viestintäpyynnön API:lle missä tahansa asiakkaassa:
case messaging:request(?EXCHANGE, tag, #time_req{opts = #{}}, self()) of
ok -> ok;
_ -> %% repeat or fail logic
end
Hajautetussa järjestelmässä komponenttien konfiguraatio voi olla hyvinkin erilainen ja pyyntöhetkellä viestintä ei ehkä vielä käynnisty tai palvelunohjain ei ole valmis palvelemaan pyyntöä. Siksi meidän on tarkistettava viestivastaus ja käsiteltävä vikatapaus.
Onnistuneen lähetyksen jälkeen asiakas saa palvelulta vastauksen tai virheilmoituksen.
Käsitellään molemmat tapaukset hand_infossa:
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-Cunked Response
On parasta välttää suurten viestien lähettämistä. Tästä riippuu koko järjestelmän reagointikyky ja vakaa toiminta. Jos kyselyyn vastaaminen vie paljon muistia, sen jakaminen osiin on pakollista.
Annan teille pari esimerkkiä tällaisista tapauksista:
- Komponentit vaihtavat binaaridataa, kuten tiedostoja. Reaktion jakaminen pieniin osiin auttaa sinua työskentelemään tehokkaasti kaikenkokoisten tiedostojen kanssa ja välttämään muistin ylivuotoja.
- Listaukset. Meidän on esimerkiksi valittava kaikki tietueet tietokannan valtavasta taulukosta ja siirrettävä ne toiseen komponenttiin.
Kutsun näitä vastauksia vetureiksi. Joka tapauksessa 1024 1 Mt:n viestiä on parempi kuin yksittäinen 1 Gt:n viesti.
Erlang-klusterissa saamme lisäetua - vähennämme vaihtopisteen ja verkon kuormitusta, koska vastaukset lähetetään välittömästi vastaanottajalle ohittaen vaihtopisteen.
Vastaa Pyynnöllä
Tämä on melko harvinainen muunnos RPC-mallista dialogijärjestelmien rakentamiseen.
Julkaise-tilaa (tietojen jakelupuu)
Tapahtumapohjaiset järjestelmät toimittavat ne kuluttajille heti, kun tiedot ovat valmiita. Siten järjestelmät ovat alttiimpia push-mallille kuin veto- tai kyselymallille. Tämän ominaisuuden avulla voit välttää resurssien tuhlaamisen pyytämällä ja odottamalla jatkuvasti tietoja.
Kuvassa näkyy viestin jakaminen tietyn aiheen tilaajille.
Klassisia esimerkkejä tämän kaavan käytöstä ovat tilajakauma: pelimaailma tietokonepeleissä, markkinatiedot pörssistä, hyödyllinen tieto tietosyötteissä.
Katsotaanpa tilaajakoodia:
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.
Lähde voi kutsua toimintoa julkaistakseen viestin missä tahansa sopivassa paikassa:
messaging:publish_message(Exchange, Key, Message).
vaihto - vaihtopisteen nimi,
avain - reititysavain
Viesti - hyötykuorma
Käänteinen Publish-subscribe
Laajentamalla pubi-alaa saat kätevän kaavan kirjaamiseen. Lähteet ja kuluttajat voivat olla täysin erilaisia. Kuvassa on tapaus, jossa on yksi kuluttaja ja useita lähteitä.
Tehtävän jakautumismalli
Lähes kaikkiin projekteihin liittyy viivästettyjen käsittelytehtävien suorittamista, kuten raporttien luomista, ilmoitusten toimittamista ja tietojen hakemista kolmannen osapuolen järjestelmistä. Näitä tehtäviä suorittavan järjestelmän suorituskykyä voidaan helposti skaalata lisäämällä käsittelijöitä. Meidän tarvitsee vain muodostaa prosessoriklusteri ja jakaa tehtävät tasaisesti niiden kesken.
Tarkastellaan tilanteita, joita syntyy 3 käsittelijän esimerkin avulla. Jo tehtäväjaon vaiheessa herää kysymys jaon oikeudenmukaisuudesta ja käsittelijöiden ylivuotosta. Round-robin-jakelu on vastuussa oikeudenmukaisuudesta, ja välttääksemme käsittelijöiden ylivuototilanteen otamme käyttöön rajoituksen prefetch_limit. Tilapäisissä olosuhteissa prefetch_limit estää yhtä käsittelijää vastaanottamasta kaikkia tehtäviä.
Viestit hallitsevat jonoja ja käsittelyprioriteettia. Prosessorit saavat tehtäviä saapuessaan. Tehtävä voidaan suorittaa onnistuneesti tai epäonnistua:
messaging:ack(Tack)
- soitetaan, jos viesti on käsitelty onnistuneestimessaging:nack(Tack)
- soitetaan kaikissa hätätilanteissa. Kun tehtävä palautetaan, viestit välittävät sen toiselle käsittelijälle.
Oletetaan, että kolmea tehtävää käsiteltäessä tapahtui monimutkainen vika: prosessori 1 kaatui tehtävän vastaanottamisen jälkeen ilman, että olisi ehtinyt ilmoittaa mitään vaihtopisteeseen. Tässä tapauksessa vaihtopiste siirtää tehtävän toiselle käsittelijälle, kun kuittausaikakatkaisu on umpeutunut. Jostain syystä käsittelijä 3 hylkäsi tehtävän ja lähetti nackin, minkä seurauksena tehtävä siirtyi myös toiselle käsittelijälle, joka suoritti sen onnistuneesti.
Alustava yhteenveto
Olemme käsitelleet hajautettujen järjestelmien perusrakenneosat ja saaneet peruskäsityksen niiden käytöstä Erlang/Elixirissä.
Yhdistämällä perusmalleja voit rakentaa monimutkaisia paradigmoja ratkaisemaan uusia ongelmia.
Sarjan viimeisessä osassa tarkastellaan yleisiä palveluiden organisoinnin, reitityksen ja tasapainotuksen kysymyksiä sekä puhutaan myös järjestelmien skaalautuvuuden ja vikasietoisuuden käytännön puolelta.
Toisen osan loppu.
Photo Shoot
Kuvat on valmistettu käyttämällä websequencediagrams.com-sivustoa
Lähde: will.com