Тархсан програмуудын барилгын блокууд. Эхний хандлага

Тархсан програмуудын барилгын блокууд. Эхний хандлага

Хамгийн сүүлд нийтлэл Бид реактив архитектурын онолын үндсийг судалж үзсэн. Өгөгдлийн урсгал, реактив Erlang/Elixir системийг хэрэгжүүлэх арга замууд болон тэдгээрт мессеж илгээх загваруудын талаар ярих цаг болжээ.

  • Хүсэлт-хариу
  • Хүсэлтийн хэсэгчилсэн хариу
  • Хүсэлтийн дагуу хариулах
  • Нийтлэх-захиалах
  • Inverted Publish-захиалах
  • Даалгаврын хуваарилалт

SOA, MSA болон Messaging

SOA, MSA нь системийг бий болгох дүрмийг тодорхойлдог системийн архитектурууд бөгөөд мессеж илгээх нь тэдгээрийг хэрэгжүүлэх үндсэн зүйлийг өгдөг.

Би энэ эсвэл тэр системийн архитектурыг сурталчлахыг хүсэхгүй байна. Би тодорхой төсөл, бизнест хамгийн үр дүнтэй, хэрэгтэй туршлагыг ашиглахын төлөө байна. Бид ямар ч парадигмыг сонгохоос үл хамааран Unix-way дээр анхаарлаа хандуулж системийн блокуудыг бий болгох нь дээр: хамгийн бага холболттой бүрэлдэхүүн хэсгүүд, бие даасан байгууллагуудыг хариуцдаг. API аргууд нь байгууллагуудтай хийх боломжтой хамгийн энгийн үйлдлүүдийг гүйцэтгэдэг.

Мессеж бол нэрнээс нь харахад мессеж зуучлагч юм. Үүний гол зорилго нь мессеж хүлээн авах, илгээх явдал юм. Энэ нь мэдээлэл дамжуулах интерфейс, систем дотор мэдээлэл дамжуулах логик сувгийг бүрдүүлэх, чиглүүлэлт, тэнцвэржүүлэх, системийн түвшинд алдаатай ажиллах үүрэгтэй.
Бидний хөгжүүлж буй мессеж нь rabbitmq-тай өрсөлдөх эсвэл солихыг оролдоогүй юм. Үүний үндсэн шинж чанарууд:

  • Хуваарилалт.
    Солилцооны цэгүүдийг бүх кластерийн зангилаанууд дээр тэдгээрийг ашигладаг кодтой аль болох ойрхон үүсгэж болно.
  • Энгийн байдал.
    Бойlerplate код, ашиглахад хялбар байдлыг багасгахад анхаарлаа хандуулаарай.
  • Илүү сайн гүйцэтгэл.
    Бид rabbitmq-ийн функцийг давтахыг хичээгээгүй бөгөөд зөвхөн OTP-д аль болох хялбархан тохирох архитектур, тээврийн давхаргыг онцолж, зардлыг бууруулна.
  • Уян хатан байдал.
    Үйлчилгээ бүр олон солилцооны загварыг нэгтгэж чаддаг.
  • Дизайнаар уян хатан байдал.
  • Өргөтгөх чадвар.
    Аппликешныг ашигласнаар мессеж өсдөг. Ачаалал ихсэх тусам та солилцооны цэгүүдийг бие даасан машин руу шилжүүлж болно.

Сэтгэгдэл бичих. Кодын зохион байгуулалтын хувьд мета төслүүд нь нарийн төвөгтэй Erlang/Elixir системүүдэд тохиромжтой. Төслийн бүх код нь нэг агуулахад байрладаг - дээвэр төсөл. Үүний зэрэгцээ микро үйлчилгээнүүд нь хамгийн их тусгаарлагдсан бөгөөд тусдаа аж ахуйн нэгжийг хариуцдаг энгийн үйлдлүүдийг гүйцэтгэдэг. Энэ аргын тусламжтайгаар бүхэл системийн API-г хадгалахад хялбар, өөрчлөлт оруулахад хялбар, нэгж, интеграцийн тест бичихэд хялбар байдаг.

Системийн бүрэлдэхүүн хэсгүүд нь шууд эсвэл брокероор дамжуулан харилцан үйлчилдэг. Мессежийн үүднээс авч үзвэл үйлчилгээ бүр амьдралын хэд хэдэн үе шаттай:

  • Үйлчилгээг эхлүүлэх.
    Энэ үе шатанд үйлчилгээг гүйцэтгэх процесс болон хамаарлыг тохируулж, ажиллуулдаг.
  • Солилцооны цэг үүсгэх.
    Үйлчилгээ нь зангилааны тохиргоонд заасан статик солилцооны цэгийг ашиглах эсвэл солилцооны цэгүүдийг динамикаар үүсгэж болно.
  • Үйлчилгээний бүртгэл.
    Үйлчилгээ нь хүсэлтэд үйлчлэхийн тулд солилцооны цэг дээр бүртгүүлсэн байх ёстой.
  • Хэвийн үйл ажиллагаа.
    Үйлчилгээ нь ашигтай ажил хийдэг.
  • Унтраах.
    2 төрлийн унтрах боломжтой: ердийн болон яаралтай. Хэвийн ажиллагааны үед үйлчилгээ нь солилцооны цэгээс салж, зогсдог. Яаралтай тохиолдолд мессеж нь дампуурлын скриптүүдийн аль нэгийг гүйцэтгэдэг.

Энэ нь нэлээд төвөгтэй мэт боловч код нь тийм ч аймшигтай биш юм. Тайлбар бүхий кодын жишээг загваруудын дүн шинжилгээнд хэсэг хугацааны дараа өгөх болно.

бирж

Солилцооны цэг нь мессежийн загвар доторх бүрэлдэхүүн хэсгүүдтэй харилцах логикийг хэрэгжүүлдэг мессежийн процесс юм. Доор үзүүлсэн бүх жишээн дээр бүрэлдэхүүн хэсгүүд нь солилцооны цэгүүдээр дамжуулан харилцан үйлчилдэг бөгөөд тэдгээрийн хослол нь мессеж үүсгэдэг.

Мессеж солилцох загварууд (MEP)

Дэлхий даяар солилцооны хэв маягийг хоёр талын болон нэг талын гэж хувааж болно. Эхнийх нь ирж буй мессежийн хариуг илэрхийлдэг бол сүүлийнх нь тийм биш юм. Үйлчлүүлэгч-серверийн архитектур дахь хоёр талын хэв маягийн сонгодог жишээ бол Хүсэлт-хариуны загвар юм. Загвар болон түүний өөрчлөлтийг харцгаая.

Хүсэлт-хариу эсвэл 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.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 ГБ хэмжээтэй нэг мессежээс хамаагүй дээр юм.

Эрланг кластерт бид нэмэлт давуу талыг олж авдаг - солилцооны цэг болон сүлжээн дэх ачааллыг бууруулдаг, учир нь хариуг шууд хүлээн авагч руу илгээж, солилцооны цэгийг тойрч гардаг.

Хүсэлтийн дагуу хариулах

Энэ бол харилцан ярианы системийг бий болгоход зориулсан 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

сэтгэгдэл нэмэх