Անցյալում
- Հարցում-պատասխան
- Հարցում-հատված պատասխան
- Պատասխան՝ հարցումով
- Հրապարակել-բաժանորդագրվել
- Inverted Publish Բաժանորդագրվել
- Առաջադրանքների բաշխում
SOA, MSA և հաղորդագրություններ
SOA-ն, MSA-ն համակարգային ճարտարապետություններ են, որոնք սահմանում են համակարգեր կառուցելու կանոնները, մինչդեռ հաղորդագրությունների փոխանակումն ապահովում է դրանց իրականացման պարզունակությունը:
Ես չեմ ուզում պրոպագանդել այս կամ այն համակարգի ճարտարապետությունը։ Ես կողմ եմ կոնկրետ նախագծի և բիզնեսի համար ամենաարդյունավետ և օգտակար պրակտիկաների կիրառմանը: Ինչպիսի պարադիգմ էլ որ ընտրենք, ավելի լավ է ստեղծել համակարգի բլոկներ՝ ուշադրություն դարձնելով Unix-ի ճանապարհին. բաղադրիչներ նվազագույն կապակցվածությամբ, որոնք պատասխանատու են առանձին սուբյեկտների համար: API մեթոդներն առավել պարզ գործողություններն են կատարում սուբյեկտների հետ:
Հաղորդագրությունների փոխանակում - ինչպես ենթադրում է անունը - հաղորդագրության միջնորդ: Դրա հիմնական նպատակը հաղորդագրություններ ստանալն ու ուղարկելն է։ Այն պատասխանատու է տեղեկատվության ուղարկման ինտերֆեյսների, համակարգում տեղեկատվության փոխանցման տրամաբանական ուղիների ձևավորման, երթուղման և հավասարակշռման, ինչպես նաև համակարգի մակարդակում ձախողումների հետ կապված խնդիրների լուծման համար:
Մշակված հաղորդագրությունները չեն փորձում մրցակցել կամ փոխարինել rabbitmq-ին: Նրա հիմնական հատկանիշները.
- Բաշխում.
Փոխանակման կետերը կարող են ստեղծվել կլաստերի բոլոր հանգույցների վրա՝ հնարավորինս մոտ դրանք օգտագործող կոդին: - Պարզություն:
Կենտրոնացեք կաթսայի ծածկագիրը նվազագույնի հասցնելու և օգտագործման հեշտության վրա: - Ավելի լավ կատարում:
Մենք չենք փորձում կրկնել rabbitmq-ի ֆունկցիոնալությունը, այլ ընտրում ենք միայն ճարտարապետական և տրանսպորտային շերտը, որը հնարավորինս պարզ կերպով տեղավորվում է OTP-ում՝ նվազագույնի հասցնելով ծախսերը: - Ճկունություն:
Յուրաքանչյուր ծառայություն կարող է միավորել բազմաթիվ փոխանակման կաղապարներ: - Ճկունություն ըստ դիզայնի:
- Մասշտաբայնություն.
Հաղորդագրություններն աճում են հավելվածի հետ: Քանի որ բեռը մեծանում է, դուք կարող եք փոխանակման կետերը տեղափոխել առանձին մեքենաներ:
Մեկնաբանություն. Կոդի կազմակերպման առումով մետա-նախագծերը լավ են համապատասխանում բարդ Erlang/Elixir համակարգերին: Ծրագրի բոլոր ծածկագրերը գտնվում են մեկ պահեստում՝ հովանու նախագիծ: Միևնույն ժամանակ, միկրոսերվիսները հնարավորինս մեկուսացված են և կատարում են պարզ գործողություններ, որոնք պատասխանատու են առանձին սուբյեկտի համար: Այս մոտեցմամբ հեշտ է պահպանել ամբողջ համակարգի API-ն, հեշտ է փոփոխություններ կատարել, հարմար է միավորի և ինտեգրացիոն թեստեր գրել։
Համակարգի բաղադրիչները փոխազդում են ուղղակիորեն կամ բրոքերի միջոցով: Հաղորդագրությունների դիրքից յուրաքանչյուր ծառայություն ունի կյանքի մի քանի փուլ.
- Ծառայության սկզբնավորում.
Այս փուլում տեղի է ունենում ծառայության և կախվածությունների կատարման գործընթացի կազմաձևումը և գործարկումը: - Ստեղծեք փոխանակման կետ:
Ծառայությունը կարող է օգտագործել ստատիկ փոխանակման կետ, որը նշված է հոսթի կազմաձևում կամ դինամիկ կերպով ստեղծել փոխանակման կետեր: - Ծառայության գրանցում.
Որպեսզի ծառայությունը սպասարկի հարցումները, այն պետք է գրանցված լինի փոխանակման կետում։ - Նորմալ շահագործում.
Ծառայությունը օգտակար աշխատանք է կատարում։ - Աշխատանքի ավարտը.
Անջատման 2 տեսակ կա՝ կանոնավոր և վթարային։ Սովորական սպասարկումով անջատվում է փոխանակման կետից ու կանգնում։ Արտակարգ դեպքերում հաղորդագրությունների փոխանակումն իրականացնում է ձախողման սցենարներից մեկը:
Այն բավականին բարդ տեսք ունի, բայց կոդը այնքան էլ սարսափելի չէ։ Կաղապարների վերլուծության մեջ մեկնաբանություններով կոդերի օրինակներ կտրվեն մի փոքր ուշ։
Փոխանակման
Փոխանակման կետը հաղորդագրությունների փոխանակման գործընթաց է, որն իրականացնում է հաղորդագրությունների ձևանմուշի բաղադրիչների հետ փոխազդեցության տրամաբանությունը: Ստորև բերված բոլոր օրինակներում բաղադրիչները փոխազդում են փոխանակման կետերի միջոցով, որոնց համակցությունը ձևավորում է հաղորդագրություններ:
Հաղորդագրությունների փոխանակման ձևեր (MEPs)
Գլոբալ առումով փոխանակման ձևերը կարելի է բաժանել երկկողմանի և միակողմանի: Առաջինները ենթադրում են արձագանք մուտքային հաղորդագրությանը, երկրորդները՝ ոչ։ Հաճախորդ-սերվեր ճարտարապետության երկկողմանի օրինաչափության դասական օրինակ է Request-response օրինաչափությունը: Հաշվի առեք ձևանմուշը և դրա փոփոխությունները:
Հարցում-պատասխան կամ RPC
RPC-ն օգտագործվում է, երբ մենք պետք է պատասխան ստանանք մեկ այլ գործընթացից: Այս գործընթացը կարող է իրականացվել նույն հյուրընկալող կամ մեկ այլ մայրցամաքում: Ստորև ներկայացված է հաղորդագրությունների միջոցով հաճախորդի և սերվերի միջև փոխգործակցության դիագրամ:
Քանի որ հաղորդագրությունները լիովին ասինխրոն են, հաճախորդի համար փոխանակումը բաժանված է 2 փուլի.
-
Հարցում ուղարկելը
messaging:request(Exchange, ResponseMatchingTag, RequestDefinition, HandlerProcess).
բորսա ‒ եզակի փոխանակման կետի անվանումը
ResponseMatchingTag ‒ տեղական պիտակ՝ պատասխանը մշակելու համար: Օրինակ՝ տարբեր օգտատերերին պատկանող մի քանի նույնական հարցումներ ուղարկելու դեպքում։
Հարցման սահմանում ‒ հարցման մարմին
HandlerProcess ‒ Կառավարչի PID: Այս գործընթացը պատասխան կստանա սերվերից: -
Պատասխանների մշակում
handle_info(#'$msg'{exchange = EXCHANGE, tag = ResponseMatchingTag,message = ResponsePayload}, State)
ResponsePayload - սերվերի պատասխան:
Սերվերի համար գործընթացը նույնպես բաղկացած է 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