Бөлінген қолданбалардың құрылыс блоктары. Бірінші тәсіл

Бөлінген қолданбалардың құрылыс блоктары. Бірінші тәсіл

Өткенде мақала Біз реактивті архитектураның теориялық негіздерін қарастырдық. Деректер ағындары, реактивті Erlang/Elixir жүйелерін енгізу жолдары және олардағы хабар алмасу үлгілері туралы айтатын кез келді:

  • Сұраныс-жауап
  • Сұранысқа бөлінген жауап
  • Сұраныспен жауап беру
  • Жариялау-жазылу
  • Inverted Publish-жазылу
  • Тапсырманы бөлу

SOA, MSA және Messaging

SOA, MSA - бұл жүйелерді құру ережелерін анықтайтын жүйелік архитектуралар, ал хабар алмасу оларды іске асыру үшін қарапайымдылықты қамтамасыз етеді.

Мен осы немесе басқа жүйе архитектурасын алға тартқым келмейді. Мен нақты жоба мен бизнес үшін ең тиімді және пайдалы тәжірибелерді қолдануды жақтаймын. Біз қандай парадигманы таңдасақ та, Unix-way бойынша жүйелік блоктарды жасаған дұрыс: минималды қосылу мүмкіндігі бар құрамдас бөліктер, жеке құрылымдарға жауапты. API әдістері нысандармен мүмкін болатын ең қарапайым әрекеттерді орындайды.

Хабар алмасу, аты айтып тұрғандай, хабар брокері. Оның негізгі мақсаты – хабарламаларды қабылдау және жіберу. Ол ақпаратты жіберуге арналған интерфейстерге, жүйе ішінде ақпаратты берудің логикалық арналарын қалыптастыруға, маршруттау мен теңгерімдеуге, сондай-ақ жүйе деңгейінде ақауларды өңдеуге жауап береді.
Біз әзірлеп жатқан хабар алмасу rabbitmq-пен бәсекелесуге немесе ауыстыруға тырыспайды. Оның негізгі ерекшеліктері:

  • Тарату.
    Алмасу нүктелерін барлық кластер түйіндерінде, оларды пайдаланатын кодқа мүмкіндігінше жақын етіп жасауға болады.
  • Қарапайымдылық
    Қондырғы кодын азайтуға және пайдаланудың қарапайымдылығына назар аударыңыз.
  • Жақсы өнімділік.
    Біз rabbitmq функционалдығын қайталауға тырыспаймыз, бірақ шығындарды барынша азайта отырып, біз OTP-ге мүмкіндігінше оңай кіретін сәулет және көлік қабатын бөлектейміз.
  • Икемділік
    Әрбір қызмет көптеген алмасу үлгілерін біріктіре алады.
  • Дизайн бойынша төзімділік.
  • Масштабтау мүмкіндігі.
    Хабар алмасу қолданбамен бірге өседі. Жүктеме ұлғайған сайын алмасу нүктелерін жеке машиналарға жылжытуға болады.

ЕСКЕРТУ. Кодты ұйымдастыру тұрғысынан мета-жобалар күрделі Erlang/Elixir жүйелеріне өте қолайлы. Барлық жоба коды бір репозиторийде орналасқан - қолшатыр жоба. Сонымен қатар, микросервистер барынша оқшауланған және жеке нысан үшін жауап беретін қарапайым операцияларды орындайды. Бұл тәсілмен бүкіл жүйенің API интерфейсін сақтау оңай, өзгертулер енгізу оңай, бірлік пен интеграциялық сынақтарды жазу ыңғайлы.

Жүйе компоненттері тікелей немесе брокер арқылы өзара әрекеттеседі. Хабар алмасу тұрғысынан әрбір қызметтің бірнеше өмірлік фазалары бар:

  • Қызметті инициализациялау.
    Бұл кезеңде қызметті орындайтын процесс және тәуелділіктер конфигурацияланады және іске қосылады.
  • Алмасу нүктесін құру.
    Қызмет түйін конфигурациясында көрсетілген статикалық алмасу нүктесін пайдалана алады немесе алмасу нүктелерін динамикалық түрде жасай алады.
  • Қызметті тіркеу.
    Қызмет сұрауларға қызмет көрсетуі үшін оны айырбастау пунктінде тіркеу керек.
  • Қалыпты жұмыс.
    Қызмет пайдалы жұмыс жасайды.
  • Жабу.
    Өшірудің екі түрі мүмкін: қалыпты және төтенше. Қалыпты жұмыс кезінде қызмет алмасу нүктесінен ажыратылады және тоқтайды. Төтенше жағдайларда хабар алмасу сәтсіздік сценарийлерінің бірін орындайды.

Бұл өте күрделі көрінеді, бірақ код соншалықты қорқынышты емес. Түсініктемелері бар код мысалдары үлгілерді талдауда сәл кейінірек беріледі.

алмасулар

Алмасу нүктесі – хабар алмасу үлгісінің ішіндегі құрамдастармен өзара әрекеттесу логикасын жүзеге асыратын хабар алмасу процесі. Төменде келтірілген барлық мысалдарда компоненттер алмасу нүктелері арқылы өзара әрекеттеседі, олардың комбинациясы хабар алмасуды құрайды.

Хабар алмасу үлгілері (ҚОҚМ)

Дүние жүзінде айырбас үлгілерін екі жақты және бір жақты деп бөлуге болады. Біріншісі кіріс хабарламаға жауапты білдіреді, екіншісі жоқ. Клиент-сервер архитектурасындағы екі жақты үлгінің классикалық мысалы Сұраныс-жауап үлгісі болып табылады. Үлгіні және оның модификацияларын қарастырайық.

Сұраныс-жауап немесе RPC

RPC басқа процесстен жауап алу қажет болғанда қолданылады. Бұл процесс бір түйінде орындалуы немесе басқа континентте орналасуы мүмкін. Төменде хабар алмасу арқылы клиент пен сервер арасындағы өзара әрекеттесу диаграммасы берілген.

Бөлінген қолданбалардың құрылыс блоктары. Бірінші тәсіл

Хабарлама толығымен асинхронды болғандықтан, клиент үшін алмасу 2 фазаға бөлінеді:

  1. Сұраныс жіберу

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

    Айырбастау ‒ айырбастау нүктесінің бірегей атауы
    ResponseMatchingTag ‒ жауапты өңдеуге арналған жергілікті белгі. Мысалы, әртүрлі пайдаланушыларға тиесілі бірнеше бірдей сұрауларды жіберу жағдайында.
    RequestDefinition - сұрау органы
    HandlerProcess ‒ өңдеушінің PID. Бұл процесс серверден жауап алады.

  2. Жауапты өңдеу

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

    ResponsePayload - сервердің жауабы.

Сервер үшін процесс сонымен қатар 2 кезеңнен тұрады:

  1. Алмасу нүктесін инициализациялау
  2. Алынған сұраныстарды өңдеу

Бұл үлгіні кодпен суреттейік. Бір ғана нақты уақыт әдісін ұсынатын қарапайым қызметті енгізу керек делік.

Сервер коды

api.hrl ішінде API сервисін анықтайық:

%% =====================================================
%%  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 Мбайт болатын 1 хабарлама 1 ГБ бір хабарламадан жақсырақ.

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 өңдеушінің мысалын пайдаланып, туындайтын жағдайларды қарастырайық. Тапсырманы бөлу сатысында да бөлудің әділдігі және өңдеушілердің толып кетуі туралы мәселе туындайды. Айналмалы жүйе бойынша бөлу әділдік үшін жауапты болады және өңдеушілердің толып кетуін болдырмау үшін біз шектеу енгіземіз. алдын ала_шектеу. Өтпелі жағдайларда алдын ала_шектеу бір өңдеушінің барлық тапсырмаларды алуына жол бермейді.

Хабарлама кезектерді және өңдеу басымдығын басқарады. Процессорлар келген кезде тапсырмаларды алады. Тапсырма сәтті немесе сәтсіз аяқталуы мүмкін:

  • messaging:ack(Tack) - егер хабарлама сәтті өңделсе, шақырылады
  • messaging:nack(Tack) - барлық төтенше жағдайларда шақырылады. Тапсырма қайтарылғаннан кейін хабар алмасу оны басқа өңдеушіге береді.

Бөлінген қолданбалардың құрылыс блоктары. Бірінші тәсіл

Үш тапсырманы өңдеу кезінде күрделі ақаулық орын алды делік: 1-процессор тапсырманы алғаннан кейін алмасу нүктесіне ештеңе хабарлауға уақыт болмай істен шықты. Бұл жағдайда айырбастау нүктесі растау күту уақыты біткеннен кейін тапсырманы басқа өңдеушіге береді. Белгілі бір себептермен өңдеуші 3 тапсырмадан бас тартты және нак жіберді; нәтижесінде тапсырма да оны сәтті орындаған басқа өңдеушіге берілді.

Алдын ала қорытынды

Біз бөлінген жүйелердің негізгі құрылыс блоктарын қарастырдық және оларды Erlang/Elixir бағдарламасында пайдалану туралы негізгі түсінікке ие болдық.

Негізгі үлгілерді біріктіру арқылы сіз пайда болатын мәселелерді шешу үшін күрделі парадигмаларды құра аласыз.

Серияның соңғы бөлігінде біз қызметтерді ұйымдастырудың, маршруттау мен теңгерімдеудің жалпы мәселелерін қарастырамыз, сонымен қатар жүйелердің масштабталатындығы мен ақауларға төзімділігінің практикалық жағы туралы айтатын боламыз.

Екінші бөлімнің соңы.

фото Мариус Кристенсен
Иллюстрациялар websequencediagrams.com арқылы дайындалған

Ақпарат көзі: www.habr.com

пікір қалдыру