Bausteng vun verdeelt Uwendungen. Éischt Approche

Bausteng vun verdeelt Uwendungen. Éischt Approche

An der leschter Artikel Mir hunn d'theoretesch Fundamenter vun der reaktiver Architektur ënnersicht. Et ass Zäit iwwer Datefloss ze schwätzen, Weeër fir reaktiv Erlang / Elixir Systemer a Messagen Musteren an hinnen ëmzesetzen:

  • Ufro-Äntwert
  • Ufro-Chunked Äntwert
  • Äntwert mat Ufro
  • Verëffentlechen-abonnéieren
  • Inverted Verëffentlechen-abonnéieren
  • Aufgab Verdeelung

SOA, MSA a Messagerie

SOA, MSA si Systemarchitekturen déi d'Regele fir Bausystemer definéieren, wärend Messagerie Primitiv fir hir Ëmsetzung ubitt.

Ech wëll dës oder déi Systemarchitektur net förderen. Ech sinn fir déi effektivsten an nëtzlech Praktiken fir e spezifesche Projet a Geschäft ze benotzen. Egal wat Paradigma mir wielen, et ass besser Systemblocken mat engem Aen op den Unix-Wee ze kreéieren: Komponenten mat minimaler Konnektivitéit, verantwortlech fir eenzel Entitéiten. API Methoden maachen déi einfachst méiglech Aktiounen mat Entitéiten.

Messagerie ass, wéi den Numm et scho seet, e Message Broker. Säin Haaptziel ass Messagen ze kréien an ze schécken. Et ass verantwortlech fir d'Interfaces fir d'Informatioun ze schécken, d'Bildung vu logesche Kanäl fir d'Iwwerdroung vun Informatioun am System, Routing a Balance, souwéi Feelerhandhabung um Systemniveau.
D'Messaging, déi mir entwéckelen, probéiert net mat Rabbitmq ze konkurréieren oder ze ersetzen. Seng Haaptmerkmale:

  • Verdeelung.
    Austauschpunkte kënnen op all Clusternoden erstallt ginn, sou no wéi méiglech un de Code deen se benotzt.
  • Simplizitéit
    Focus op d'Minimaliséierung vun der Boilerplate Code an d'Benotzungsfrëndlechkeet.
  • Besser Leeschtung.
    Mir probéieren net d'Funktionalitéit vun rabbitmq ze widderhuelen, mee markéieren nëmmen d'architektonesch an Transportschicht, déi mir an den OTP sou einfach wéi méiglech passen, d'Käschte minimiséieren.
  • Flexibilitéit.
    All Service kann vill Austausch Template kombinéieren.
  • Widderstandsfäegkeet duerch Design.
  • Skalierbarkeet.
    Messagerie wiisst mat der Applikatioun. Wéi d'Laascht eropgeet, kënnt Dir den Austauschpunkte op eenzel Maschinnen réckelen.

Kommentéiert. Wat d'Codeorganisatioun ugeet, sinn Meta-Projete gutt fir komplex Erlang / Elixir Systemer. All Projet Code ass an engem Repository lokaliséiert - e Regenschirmprojet. Zur selwechter Zäit si Mikroservicer maximal isoléiert a maachen einfach Operatiounen, déi fir eng separat Entitéit verantwortlech sinn. Mat dëser Approche ass et einfach d'API vum ganze System z'erhalen, et ass einfach Ännerungen ze maachen, et ass bequem Eenheets- an Integratiounstester ze schreiwen.

D'Systemkomponente interagéieren direkt oder duerch e Broker. Aus enger Messagerie Perspektiv huet all Service verschidde Liewensphasen:

  • Service Initialiséierung.
    Op dëser Etapp sinn de Prozess an Ofhängegkeeten, déi de Service ausféieren, konfiguréiert a lancéiert.
  • Schafen vun engem Austausch Punkt.
    De Service kann e statesche Austauschpunkt benotzen, deen an der Nodekonfiguratioun uginn ass, oder Austauschpunkte dynamesch erstellen.
  • Service Aschreiwung.
    Fir de Service Ufroen ze déngen, muss et um Austauschpunkt registréiert ginn.
  • Normale Fonctionnement.
    De Service produzéiert nëtzlech Aarbecht.
  • Ausmaachen.
    Et ginn 2 Aarte vu Shutdown méiglech: normal an Noutfall. Wärend der normaler Operatioun gëtt de Service vum Austauschpunkt getrennt a stoppt. An Noutsituatiounen féiert Messagerie ee vun de Failover Scripten.

Et gesäit zimlech komplizéiert aus, awer de Code ass net sou grujeleg. Code Beispiller mat Kommentaren ginn an der Analyse vun Templates e bësse méi spéit ginn.

Austausch

Exchange Point ass e Messagerieprozess deen d'Logik vun der Interaktioun mat Komponenten an der Messaging Template implementéiert. An all de Beispiller hei ënnendrënner interagéieren d'Komponente duerch Austauschpunkten, d'Kombinatioun vun deenen d'Messaging formt.

Messageaustauschmuster (MEPs)

Globalt kënnen Austauschmuster an zwee-Wee an een-Wee opgedeelt ginn. Déi fréier implizéiert eng Äntwert op eng erakommen Noriicht, déi lescht net. E klassescht Beispill vun engem zwee-Wee Muster an der Client-Server Architektur ass d'Request-Response Muster. Loosst eis d'Schabloun a seng Ännerungen kucken.

Ufro-Äntwert oder RPC

RPC gëtt benotzt wa mir eng Äntwert vun engem anere Prozess musse kréien. Dëse Prozess kann um selwechten Node lafen oder op engem anere Kontinent sinn. Drënner ass en Diagramm vun der Interaktioun tëscht Client a Server iwwer Messagerie.

Bausteng vun verdeelt Uwendungen. Éischt Approche

Well Messagerie komplett asynchron ass, ass den Austausch fir de Client an 2 Phasen opgedeelt:

  1. Ufro ze schécken

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

    Exchange ‒ eenzegaartegen Numm vum Austauschpunkt
    ResponseMatchingTag ‒ lokal Label fir d'Veraarbechtung vun der Äntwert. Zum Beispill, am Fall vun schéckt e puer identesch Ufroe gehéiert zu verschiddene Benotzer.
    RequestDefinition - Ufro Kierper
    Handler Prozess ‒ PID vum Handler. Dëse Prozess kritt eng Äntwert vum Server.

  2. Veraarbechtung vun der Äntwert

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

    ÄntwertPayload - Server Äntwert.

Fir de Server besteet de Prozess och aus 2 Phasen:

  1. Initialiséiere vum Austauschpunkt
  2. Veraarbechtung vun kritt Demanden

Loosst eis dës Schabloun mat Code illustréieren. Loosst eis soen, mir mussen en einfachen Service ëmsetzen deen eng eenzeg exakt Zäitmethod ubitt.

Server Code

Loosst eis de Service API an api.hrl definéieren:

%% =====================================================
%%  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{}
}).

Loosst eis de Service Controller an time_controller.erl definéieren

%% В примере показан только значимый код. Вставив его в шаблон 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.

Client Code

Fir eng Ufro un de Service ze schécken, kënnt Dir d'Messaging Ufro API iwwerall am Client nennen:

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

An engem verdeelte System kann d'Konfiguratioun vun de Komponenten ganz ënnerschiddlech sinn an zum Zäitpunkt vun der Ufro kann d'Message nach net starten, oder de Service Controller ass net prett fir d'Ufro ze servéieren. Dofir musse mir d'Message-Äntwert iwwerpréiwen an de Feelerfall behandelen.
No der erfollegräicher Sendung kritt de Client eng Äntwert oder Feeler vum Service.
Loosst eis béid Fäll an handle_info behandelen:

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};

Ufro-Chunked Äntwert

Et ass am beschten fir grouss Messagen ze vermeiden. D'Reaktiounsfäegkeet an d'stabil Operatioun vum ganze System hänkt dovun of. Wann d'Äntwert op eng Ufro vill Erënnerung ophëlt, ass et obligatoresch opzedeelen an Deeler.

Bausteng vun verdeelt Uwendungen. Éischt Approche

Loosst mech Iech e puer Beispiller vun esou Fäll ginn:

  • D'Komponenten austauschen binär Daten, wéi Dateien. D'Äntwert a kleng Deeler ze briechen hëlleft Iech effizient mat Dateie vun all Gréisst ze schaffen an Erënnerungsiwwerlaf ze vermeiden.
  • Oplëschtungen. Zum Beispill musse mir all records aus enger riseger Tabell an der Datebank auswielen an se op eng aner Komponent transferéieren.

Ech nennen dës Äntwerten Lokomotiv. Op alle Fall sinn 1024 Messagen vun 1 MB besser wéi eng eenzeg Noriicht vun 1 GB.

Am Erlang-Cluster kréie mir en zousätzleche Virdeel - d'Reduktioun vun der Belaaschtung op den Austauschpunkt an dem Netz, well d'Äntwerten direkt un den Empfänger geschéckt ginn, den Austauschpunkt ëmgoen.

Äntwert mat Ufro

Dëst ass eng zimlech rar Ännerung vum RPC Muster fir Dialogsystemer ze bauen.

Bausteng vun verdeelt Uwendungen. Éischt Approche

Verëffentlechen-abonnéieren (Datenverdeelungsbaum)

Event-driven Systemer liwweren se un d'Konsumenten soubal d'Donnéeë prett sinn. Also, Systemer si méi ufälleg fir e Push-Modell wéi zu engem Pull- oder Ëmfromodell. Dës Fonktioun erlaabt Iech Ressourcen ze vermeiden andeems Dir dauernd op Daten freet a waart.
D'Figur weist de Prozess fir e Message un d'Konsumenten ze verdeelen, déi op e spezifescht Thema abonnéiert sinn.

Bausteng vun verdeelt Uwendungen. Éischt Approche

Klassesch Beispiller fir dëst Muster ze benotzen sinn d'Verdeelung vum Staat: d'Spillwelt an Computerspiller, Maartdaten iwwer Austausch, nëtzlech Informatioun an Datenfeeds.

Loosst eis den Abonnentcode kucken:

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.

D'Quell kann d'Funktioun nennen fir e Message op all praktesch Plaz ze publizéieren:

messaging:publish_message(Exchange, Key, Message).

Exchange - Numm vum Austauschpunkt,
Schlëssel - Routing Schlëssel
Message - Notzlaascht

Inverted Verëffentlechen-abonnéieren

Bausteng vun verdeelt Uwendungen. Éischt Approche

Andeems Dir Pub-Sub erweidert, kënnt Dir e Muster bequem fir ze loggen kréien. De Set vu Quellen a Konsumenten ka komplett anescht sinn. D'Figur weist e Fall mat engem Konsument a verschidde Quellen.

Aufgab Verdeelung Muster

Bal all Projet involvéiert ausgestallte Veraarbechtungsaufgaben, sou wéi Berichter generéieren, Notifikatiounen liwweren an Daten aus Drëtt-Partei Systemer zréckzéien. Den Duerchgang vum System, deen dës Aufgaben ausféiert, kann einfach skaléiert ginn andeems Dir Handler bäidréit. Alles wat fir eis bleift ass e Stärekoup vu Prozessoren ze bilden an d'Aufgaben gläichméisseg tëscht hinnen ze verdeelen.

Loosst eis d'Situatiounen kucken, déi entstinn mam Beispill vun 3 Handler. Och an der Etapp vun der Aufgab Verdeelung, stellt d'Fro vun Fairness vun Verdeelung an Iwwerfloss vun Handler. Ronn-Robin Verdeelung wäert fir Fairness verantwortlech sinn, a fir eng Situatioun vun Iwwerfloss vun Handler ze vermeiden, wäerte mir eng Restriktioun aféieren prefetch_limit. An transient Konditiounen prefetch_limit wäert verhënneren datt een Handler all Aufgaben kritt.

Messagerie geréiert Schlaangen an Veraarbechtung Prioritéit. Prozessoren kréien Aufgaben wéi se ukommen. D'Aufgab kann erfollegräich ofgeschloss oder versoen:

  • messaging:ack(Tack) - genannt wann de Message erfollegräich veraarbecht ass
  • messaging:nack(Tack) - an all Noutsituatioun opgeruff. Wann d'Aufgab zréckgeet, gëtt d'Message se un en aneren Handler weiderginn.

Bausteng vun verdeelt Uwendungen. Éischt Approche

Stellt Iech vir, datt e komplexe Feeler beim Veraarbechtung vun dräi Aufgaben geschitt ass: Prozessor 1, nodeems hien d'Aufgab krut, ass ofgebrach ouni Zäit ze hunn fir eppes un den Austauschpunkt ze mellen. An dësem Fall wäert den Austauschpunkt d'Aufgab op en aneren Handler iwwerdroen nodeems den ack-Timeout ofgelaf ass. Aus e puer Grënn huet den Handler 3 d'Aufgab opginn an Nack geschéckt; als Resultat gouf d'Aufgab och un en aneren Handler transferéiert deen et erfollegräich ofgeschloss huet.

Virleefeg Zesummefaassung

Mir hunn d'Basis Bausteng vun verdeelt Systemer ofgedeckt an e Basisverständnis vun hirer Notzung am Erlang / Elixir kritt.

Andeems Dir Basismuster kombinéiere kënnt Dir komplex Paradigme bauen fir opkomende Probleemer ze léisen.

Am leschten Deel vun der Serie wäerte mir allgemeng Themen vun der Organisatioun vu Servicer, Routing a Balance kucken, an och iwwer déi praktesch Säit vun der Skalierbarkeet a Feelertoleranz vu Systemer schwätzen.

Enn vum zweeten Deel.

Foto Marius Christensen
Illustratiounen virbereet mat websequencediagrams.com

Source: will.com

Setzt e Commentaire