Բաշխված հավելվածների շինանյութեր: Առաջին մոտեցում

Բաշխված հավելվածների շինանյութեր: Առաջին մոտեցում

Անցյալում Հոդված մենք վերլուծել ենք ռեակտիվ ճարտարապետության տեսական հիմունքները։ Ժամանակն է խոսել տվյալների հոսքերի, ռեակտիվ Erlang/Elixir համակարգերի ներդրման ուղիների և դրանցում հաղորդագրությունների օրինաչափությունների մասին.

  • Հարցում-պատասխան
  • Հարցում-հատված պատասխան
  • Պատասխան՝ հարցումով
  • Հրապարակել-բաժանորդագրվել
  • Inverted Publish Բաժանորդագրվել
  • Առաջադրանքների բաշխում

SOA, MSA և հաղորդագրություններ

SOA-ն, MSA-ն համակարգային ճարտարապետություններ են, որոնք սահմանում են համակարգեր կառուցելու կանոնները, մինչդեռ հաղորդագրությունների փոխանակումն ապահովում է դրանց իրականացման պարզունակությունը:

Ես չեմ ուզում պրոպագանդել այս կամ այն ​​համակարգի ճարտարապետությունը։ Ես կողմ եմ կոնկրետ նախագծի և բիզնեսի համար ամենաարդյունավետ և օգտակար պրակտիկաների կիրառմանը: Ինչպիսի պարադիգմ էլ որ ընտրենք, ավելի լավ է ստեղծել համակարգի բլոկներ՝ ուշադրություն դարձնելով Unix-ի ճանապարհին. բաղադրիչներ նվազագույն կապակցվածությամբ, որոնք պատասխանատու են առանձին սուբյեկտների համար: API մեթոդներն առավել պարզ գործողություններն են կատարում սուբյեկտների հետ:

Հաղորդագրությունների փոխանակում - ինչպես ենթադրում է անունը - հաղորդագրության միջնորդ: Դրա հիմնական նպատակը հաղորդագրություններ ստանալն ու ուղարկելն է։ Այն պատասխանատու է տեղեկատվության ուղարկման ինտերֆեյսների, համակարգում տեղեկատվության փոխանցման տրամաբանական ուղիների ձևավորման, երթուղման և հավասարակշռման, ինչպես նաև համակարգի մակարդակում ձախողումների հետ կապված խնդիրների լուծման համար:
Մշակված հաղորդագրությունները չեն փորձում մրցակցել կամ փոխարինել rabbitmq-ին: Նրա հիմնական հատկանիշները.

  • Բաշխում.
    Փոխանակման կետերը կարող են ստեղծվել կլաստերի բոլոր հանգույցների վրա՝ հնարավորինս մոտ դրանք օգտագործող կոդին:
  • Պարզություն:
    Կենտրոնացեք կաթսայի ծածկագիրը նվազագույնի հասցնելու և օգտագործման հեշտության վրա:
  • Ավելի լավ կատարում:
    Մենք չենք փորձում կրկնել rabbitmq-ի ֆունկցիոնալությունը, այլ ընտրում ենք միայն ճարտարապետական ​​և տրանսպորտային շերտը, որը հնարավորինս պարզ կերպով տեղավորվում է OTP-ում՝ նվազագույնի հասցնելով ծախսերը:
  • Ճկունություն:
    Յուրաքանչյուր ծառայություն կարող է միավորել բազմաթիվ փոխանակման կաղապարներ:
  • Ճկունություն ըստ դիզայնի:
  • Մասշտաբայնություն.
    Հաղորդագրություններն աճում են հավելվածի հետ: Քանի որ բեռը մեծանում է, դուք կարող եք փոխանակման կետերը տեղափոխել առանձին մեքենաներ:

Մեկնաբանություն. Կոդի կազմակերպման առումով մետա-նախագծերը լավ են համապատասխանում բարդ Erlang/Elixir համակարգերին: Ծրագրի բոլոր ծածկագրերը գտնվում են մեկ պահեստում՝ հովանու նախագիծ: Միևնույն ժամանակ, միկրոսերվիսները հնարավորինս մեկուսացված են և կատարում են պարզ գործողություններ, որոնք պատասխանատու են առանձին սուբյեկտի համար: Այս մոտեցմամբ հեշտ է պահպանել ամբողջ համակարգի API-ն, հեշտ է փոփոխություններ կատարել, հարմար է միավորի և ինտեգրացիոն թեստեր գրել։

Համակարգի բաղադրիչները փոխազդում են ուղղակիորեն կամ բրոքերի միջոցով: Հաղորդագրությունների դիրքից յուրաքանչյուր ծառայություն ունի կյանքի մի քանի փուլ.

  • Ծառայության սկզբնավորում.
    Այս փուլում տեղի է ունենում ծառայության և կախվածությունների կատարման գործընթացի կազմաձևումը և գործարկումը:
  • Ստեղծեք փոխանակման կետ:
    Ծառայությունը կարող է օգտագործել ստատիկ փոխանակման կետ, որը նշված է հոսթի կազմաձևում կամ դինամիկ կերպով ստեղծել փոխանակման կետեր:
  • Ծառայության գրանցում.
    Որպեսզի ծառայությունը սպասարկի հարցումները, այն պետք է գրանցված լինի փոխանակման կետում։
  • Նորմալ շահագործում.
    Ծառայությունը օգտակար աշխատանք է կատարում։
  • Աշխատանքի ավարտը.
    Անջատման 2 տեսակ կա՝ կանոնավոր և վթարային։ Սովորական սպասարկումով անջատվում է փոխանակման կետից ու կանգնում։ Արտակարգ դեպքերում հաղորդագրությունների փոխանակումն իրականացնում է ձախողման սցենարներից մեկը:

Այն բավականին բարդ տեսք ունի, բայց կոդը այնքան էլ սարսափելի չէ։ Կաղապարների վերլուծության մեջ մեկնաբանություններով կոդերի օրինակներ կտրվեն մի փոքր ուշ։

Փոխանակման

Փոխանակման կետը հաղորդագրությունների փոխանակման գործընթաց է, որն իրականացնում է հաղորդագրությունների ձևանմուշի բաղադրիչների հետ փոխազդեցության տրամաբանությունը: Ստորև բերված բոլոր օրինակներում բաղադրիչները փոխազդում են փոխանակման կետերի միջոցով, որոնց համակցությունը ձևավորում է հաղորդագրություններ:

Հաղորդագրությունների փոխանակման ձևեր (MEPs)

Գլոբալ առումով փոխանակման ձևերը կարելի է բաժանել երկկողմանի և միակողմանի: Առաջինները ենթադրում են արձագանք մուտքային հաղորդագրությանը, երկրորդները՝ ոչ։ Հաճախորդ-սերվեր ճարտարապետության երկկողմանի օրինաչափության դասական օրինակ է Request-response օրինաչափությունը: Հաշվի առեք ձևանմուշը և դրա փոփոխությունները:

Հարցում-պատասխան կամ RPC

RPC-ն օգտագործվում է, երբ մենք պետք է պատասխան ստանանք մեկ այլ գործընթացից: Այս գործընթացը կարող է իրականացվել նույն հյուրընկալող կամ մեկ այլ մայրցամաքում: Ստորև ներկայացված է հաղորդագրությունների միջոցով հաճախորդի և սերվերի միջև փոխգործակցության դիագրամ:

Բաշխված հավելվածների շինանյութեր: Առաջին մոտեցում

Քանի որ հաղորդագրությունները լիովին ասինխրոն են, հաճախորդի համար փոխանակումը բաժանված է 2 փուլի.

  1. Հարցում ուղարկելը

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

    բորսա ‒ եզակի փոխանակման կետի անվանումը
    ResponseMatchingTag ‒ տեղական պիտակ՝ պատասխանը մշակելու համար: Օրինակ՝ տարբեր օգտատերերին պատկանող մի քանի նույնական հարցումներ ուղարկելու դեպքում։
    Հարցման սահմանում ‒ հարցման մարմին
    HandlerProcess ‒ Կառավարչի PID: Այս գործընթացը պատասխան կստանա սերվերից:

  2. Պատասխանների մշակում

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

    ResponsePayload - սերվերի պատասխան:

Սերվերի համար գործընթացը նույնպես բաղկացած է 2 փուլից.

  1. Փոխանակման կետի սկզբնավորում
  2. Մուտքային հարցումների մշակում

Եկեք այս ձևանմուշը նկարազարդենք կոդով։ Ենթադրենք, որ մենք պետք է ներդնենք պարզ ծառայություն, որն ապահովում է մեկ ճշգրիտ ժամանակի մեթոդ:

Սերվերի կոդը

Եկեք տեղափոխենք ծառայության 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{}
}).

Սահմանեք ծառայության վերահսկիչը 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.

Հաճախորդի կոդը

Ծառայությանը հարցում ուղարկելու համար կարող եք զանգահարել հաղորդագրությունների հարցումների API-ին հաճախորդի ցանկացած վայրում՝

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

Բաշխված համակարգում բաղադրիչների կոնֆիգուրացիան կարող է շատ տարբեր լինել, և հարցման պահին հաղորդագրությունները կարող են դեռ չսկսվել, կամ ծառայության վերահսկիչը պատրաստ չի լինի սպասարկել հարցումը: Հետևաբար, մենք պետք է ստուգենք հաղորդագրությունների պատասխանը և կարգավորենք ձախողման դեպքը:
Հաճախորդին հաջողությամբ ուղարկելուց հետո ծառայությունը կստանա պատասխան կամ սխալ:
Եկեք կարգավորենք երկու դեպքերը 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};

Հարցում-հատված պատասխան

Ավելի լավ է խուսափել հսկայական հաղորդագրություններ ուղարկելուց: Սրանից է կախված ողջ համակարգի արձագանքունակությունն ու կայուն աշխատանքը: Եթե ​​հարցման պատասխանը մեծ հիշողություն է խլում, ապա բաժանումը պարտադիր է:

Բաշխված հավելվածների շինանյութեր: Առաջին մոտեցում

Ահա այսպիսի դեպքերի մի քանի օրինակ.

  • Բաղադրիչները փոխանակում են երկուական տվյալներ, ինչպիսիք են ֆայլերը: Պատասխանը փոքր մասերի բաժանելը օգնում է արդյունավետ աշխատել ցանկացած չափի ֆայլերի հետ և չնկատել հիշողության գերհոսքերը:
  • Ցուցակներ. Օրինակ, մենք պետք է ընտրենք բոլոր գրառումները տվյալների բազայի հսկայական աղյուսակից և փոխանցենք այն մեկ այլ բաղադրիչի:

Ես նման արձագանքներն անվանում եմ լոկոմոտիվ։ Ամեն դեպքում, 1024 1MB հաղորդագրությունները ավելի լավ են, քան մեկ 1GB հաղորդագրությունը:

Erlang կլաստերում մենք ստանում ենք լրացուցիչ առավելություն՝ նվազեցնելով բեռը փոխանակման կետի և ցանցի վրա, քանի որ պատասխաններն անմիջապես ուղարկվում են ստացողին՝ շրջանցելով փոխանակման կետը:

Պատասխան՝ հարցումով

Սա խոսակցական համակարգեր կառուցելու համար RPC օրինաչափության բավականին հազվադեպ փոփոխություն է:

Բաշխված հավելվածների շինանյութեր: Առաջին մոտեցում

Հրապարակել-բաժանորդագրվել (տվյալների բաշխման ծառ)

Իրադարձությունների վրա հիմնված համակարգերը տրամադրում են տվյալներ սպառողներին, երբ դրանք հասանելի են դառնում: Այսպիսով, համակարգերն ավելի հակված են մղման մոդելին, քան ձգման կամ հարցման մոդելին: Այս հատկությունը թույլ է տալիս չվատնել ռեսուրսները՝ անընդհատ տվյալներ խնդրելով և սպասելով:
Նկարը ցույց է տալիս որոշակի թեմայի բաժանորդագրված սպառողներին հաղորդագրություն տարածելու գործընթացը:

Բաշխված հավելվածների շինանյութեր: Առաջին մոտեցում

Այս օրինաչափության օգտագործման դասական օրինակներն են վիճակի բաշխումը. խաղային աշխարհը համակարգչային խաղերում, շուկայական տվյալներ փոխանակումների վերաբերյալ, օգտակար տեղեկատվություն տվյալների հոսքերում:

Հաշվի առեք բաժանորդի կոդը.

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.

Աղբյուրը կարող է զանգահարել հաղորդագրության հրապարակման գործառույթը ցանկացած հարմար վայրում.

messaging:publish_message(Exchange, Key, Message).

բորսա - փոխանակման կետի անվանումը,
Բանալի ‒ երթուղային բանալին
հաղորդագրություն - օգտակար բեռ

Inverted Publish Բաժանորդագրվել

Բաշխված հավելվածների շինանյութեր: Առաջին մոտեցում

Տեղադրելով pub-sub-ը, դուք կարող եք ստանալ մի օրինակ, որը հարմար է անտառահատումների համար: Աղբյուրների և սպառողների հավաքածուն կարող է բոլորովին տարբեր լինել: Նկարը ցույց է տալիս մեկ սպառողի և բազմաթիվ աղբյուրների դեպքը:

Առաջադրանքների բաշխման օրինաչափություն

Գրեթե յուրաքանչյուր նախագծում կան հետաձգված մշակման խնդիրներ, ինչպիսիք են հաշվետվությունների ստեղծումը, ծանուցումների առաքումը և երրորդ կողմի համակարգերից տվյալներ ստանալը: Այս առաջադրանքները կատարող համակարգի թողունակությունը հեշտությամբ չափվում է՝ ավելացնելով պրոցեսորներ: Մեզ մնում է պրոցեսորների կլաստերի ձևավորումն ու առաջադրանքների միջև հավասարաչափ բաշխումը։

Մտածեք այն իրավիճակները, որոնք առաջանում են՝ օգտագործելով 3 մշակողների օրինակը: Նույնիսկ առաջադրանքների բաշխման փուլում առաջանում է կարգավորողների բաշխման և գերհոսքի արդարության հարցը: Շրջանակային բաշխումը պատասխանատվություն է կրելու արդարության համար, և որպեսզի խուսափենք բեռնափոխադրողների հորդառատ իրավիճակից, մենք սահմանափակում ենք մտցնելու. prefetch_limit. Անցումային ռեժիմներում prefetch_limit թույլ չի տա մեկ կառավարչի ստանալ բոլոր առաջադրանքները:

Հաղորդագրությունները կառավարում են հերթերը և մշակման առաջնահերթությունը: Պրոցեսորները ստանում են առաջադրանքներ, երբ նրանք գալիս են: Առաջադրանքը կարող է հաջողությամբ ավարտվել կամ ձախողվել.

  • messaging:ack(Tack) ‒ զանգահարել հաղորդագրության հաջող մշակման դեպքում
  • messaging:nack(Tack) - զանգահարել բոլոր արտակարգ իրավիճակներում: Առաջադրանքը վերադառնալուց հետո հաղորդագրություններն այն կփոխանցեն մեկ այլ մշակողի:

Բաշխված հավելվածների շինանյութեր: Առաջին մոտեցում

Ենթադրենք, որ երեք առաջադրանք մշակելիս տեղի է ունեցել բարդ ձախողում. կարգավորիչ 1-ը, առաջադրանքը ստանալուց հետո, վթարի է ենթարկվել՝ չհասցնելով որևէ բան հաղորդել փոխանակման կետին: Այս դեպքում փոխանակման կետը աշխատանքը կփոխանցի մեկ այլ կառավարչի՝ ընդունելության ժամանակի ավարտից հետո: Handler 3-ը ինչ-ինչ պատճառներով լքեց առաջադրանքը և ուղարկեց նեյք, արդյունքում առաջադրանքը փոխանցվեց նաև մեկ այլ մշակողի, որը հաջողությամբ ավարտեց այն:

Նախնական ամփոփում

Մենք բաժանել ենք բաշխված համակարգերի հիմնական կառուցվածքային բլոկները և ձեռք ենք բերել հիմնական պատկերացում Erlang/Elixir-ում դրանց օգտագործման վերաբերյալ:

Համակցելով հիմնական կաղապարները՝ կարող են ստեղծվել բարդ պարադիգմներ՝ առաջացող խնդիրները լուծելու համար:

Ցիկլի վերջին մասում մենք կդիտարկենք ծառայությունների կազմակերպման, երթուղման և հավասարակշռման ընդհանուր հարցերը, ինչպես նաև կխոսենք համակարգերի մասշտաբայնության և սխալների հանդուրժողականության գործնական կողմի մասին:

Երկրորդ մասի ավարտ.

լուսանկար Մարիուս Քրիստենսեն
Նկարազարդումները՝ websequencediagrams.com-ի կողմից

Source: www.habr.com

Добавить комментарий