Будаўнічыя блокі размеркаваных прыкладанняў. Другое набліжэнне

анонс

Калегі, у сярэдзіне лета я планую выпусціць яшчэ адзін цыкл артыкулаў па праектаванні сістэм масавага абслугоўвання: "Эксперымент VTrade" - спроба напісаць фрэймворк для гандлёвых сістэм. У цыкле будзе разабрана тэорыя і практыка пабудовы біржы, аўкцыёну і магазіна. У канцы артыкула прапаную прагаласаваць за найболей цікавыя вам тэмы.

Будаўнічыя блокі размеркаваных прыкладанняў. Другое набліжэнне

Гэта завяршальны артыкул цыклу па размеркаваных рэактыўных прыкладаннях на Erlang / Elixir. У першым артыкуле можна знайсці тэарэтычныя асновы рэактыўнай архітэктуры. Другі артыкул ілюструе асноўныя шаблоны і механізмы пабудовы падобных сістэм.

Сёння мы ўзнімем пытанні развіцця кодавай базы і праектаў у цэлым.

Арганізацыя сэрвісаў

У рэальным жыцці пры распрацоўцы сэрвісу часта даводзіцца аб'ядноўваць некалькі шаблонаў узаемадзеяння ў адным кантролеры. Напрыклад, сэрвіс users, вырашальны задачы кіравання профілямі карыстачоў праекту, павінен адказваць на запыты req-resp і паведамляць аб абнаўленнях профіляў праз pub-sub. Гэты выпадак даволі просты: за messaging варта адзін кантролер, які рэалізуе логіку сэрвісу і які публікуе абнаўленні.

Сітуацыя ўскладняецца, калі нам трэба рэалізаваць адмоваўстойлівы размеркаваны сэрвіс. Уявім, што патрабаванні да users змяніліся:

  1. зараз сэрвіс павінен апрацоўваць запыты на 5 вузлах кластара,
  2. мець магчымасць выканання фонавых задач апрацоўкі,
  3. а таксама ўмець дынамічна кіраваць спісамі падпіскі на абнаўленні профіляў.

заўвага: Пытанне кансістэнтнага захоўвання і рэплікацыі дадзеных мы не разглядаем. Выкажам здагадку, што гэтыя пытанні вырашаны раней і ў сістэме ўжо існуе надзейны і які маштабуецца пласт захоўвання, а апрацоўшчыкі маюць механізмы ўзаемадзеяння з ім.

Фармальнае апісанне сэрвісу users ускладнілася. З пункту гледжання праграміста дзякуючы выкарыстанню messaging змены мінімальныя. Каб задаволіць першае патрабаванне, нам трэба наладзіць балансаванне на кропцы абмену req-resp.

Патрабаванне апрацоўкі фонавых задач узнікае часта. У users гэта могуць быць праверкі дакументаў карыстачоў, апрацоўка загружанага мультымедыя, ці сінхранізацыя дадзеных з сац. сеткамі. Гэтыя задачы трэба неяк размяркоўваць у рамках кластара і кантраляваць ход выканання. Таму ў нас два варыянты рашэння: альбо выкарыстаць шаблон размеркавання задач з мінулага артыкула, альбо, калі ён не падыходзіць, напісаць кастамны планавальнік задач, які будзе неабходнай нам выявай кіраваць пулам апрацоўшчыкаў.

3 пункт патрабуе пашырэння шаблону pub-sub. І для рэалізацыі, пасля стварэння кропкі абмену pub-sub, нам неабходна дадаткова запусціць кантролер гэтага кропка ў рамках нашага сэрвісу. Такім чынам, мы як быццам выносім логіку апрацоўкі падпіскі і адпіскі з пласта messaging у рэалізацыю users.

У выніку, дэкампазіцыя задачы паказала, што для задавальнення патрабаванняў нам трэба запусціць на розных вузлах 5 экзэмпляраў сэрвісу і стварыць дадатковую сутнасць - кантролер pub-sub, які адказвае за падпіску.
Для запуску 5 апрацоўшчыкаў не патрабуецца мяняць код сэрвісу. Адзінае дадатковае дзеянне - настройка правілаў балансавання на кропцы абмену, аб чым мы пагаворым крыху пазней.
Таксама з'явілася дадатковая складанасць: кантролер pub-sub і кастамны планавальнік задач павінны працаваць у адзіным асобніку. Ізноў жа, сэрвіс messaging, як фундаментальны, павінен падаваць механізм выбару лідэра.

Выбар лідэра

У размеркаваных сістэмах выбар лідэра - працэдура прызначэння адзінага працэсу, які адказвае за планаванне размеркаванай апрацоўкі нейкай нагрузкі.

У сістэмах, не схільных да цэнтралізацыі, знаходзяць ужыванне ўніверсальныя алгарытмы і алгарытмы на аснове кансэнсусу, напрыклад paxos або raft.
Паколькі messaging - гэта брокер і цэнтральны элемент, то ён ведае пра ўсіх кантролераў сэрвісу - кандыдатаў у лідэры. Messaging можа прызначаць лідэра без галасавання.

Усе сэрвісы пасля старту і падлучэнні да кропкі абмену атрымліваюць сістэмнае паведамленне #'$leader'{exchange = ?EXCHANGE, pid = LeaderPid, servers = Servers}. Калі LeaderPid супадае з pid бягучага працэсу, ён прызначаецца лідэрам, а спіс Servers уключае ў сябе ўсе вузлы і іх параметры.
У момант з'яўлення новага і адключэнні працавальнага вузла кластара, усе кантролеры сэрвісу атрымліваюць #'$slave_up'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts} и #'$slave_down'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts} адпаведна.

Такім чынам, усе кампаненты ведаюць аб усіх зменах, і ў кластары ў кожны момант часу гарантавана адзін лідэр.

Пасрэднікі

Для рэалізацыі складаных размеркаваных працэсаў апрацоўкі, а таксама ў задачах аптымізацыі ўжо існуючай архітэктуры зручна прымяняць пасрэднікаў.
Каб не мяняць код сэрвісаў і вырашаць, напрыклад, задачы дадатковай апрацоўкі, маршрутызацыі ці лагіравання паведамленняў, перад сэрвісам можна ўключыць проксі-апрацоўшчык, які выканае ўсю дадатковую працу.

Класічным прыкладам аптымізацыі pub-sub з'яўляецца размеркаванае прыкладанне з бізнэс-ядром, якія генеруюць падзеі абнаўленняў, напрыклад змена кошту на рынку, і пласт доступу – N сервераў, якія прадстаўляюць websocket API для web кліентаў.
Калі вырашаць "у лоб", то абслугоўванне кліента выглядае наступным чынам:

  • кліент устанаўлівае злучэнні з платформай. На боку сервера, які тэрмінуе трафік, адбываецца запуск працэсу, які абслугоўвае гэтае падлучэнне.
  • у кантэксце абслуговага працэсу адбываецца аўтарызацыя і падпіска на абнаўленні. Працэс выклікае метад subscribe для топікаў.
  • пасля генерацыі падзеі ў ядры яно дастаўляецца працэсам, якія абслугоўваюць падлучэнні.

Уявім, што ў нас 50000 падпісчыкаў на топік "news". Падпісчыкі размеркаваны па 5 серверах раўнамерна. У выніку кожнае абнаўленне, прыйдучы на ​​кропку абмену, будзе рэплікавана 50000 разоў: 10000 разоў на кожны сервер, па колькасці падпісчыкаў на ім. Не зусім эфектыўная схема, праўда?
Каб палепшыць сітуацыю, увядзем проксі, які мае адно і тое ж імя з пунктам абмену. Рэгістратар глабальных імёнаў павінен умець вяртаць найблізкі працэс па імі, гэта важна.

Запусцім гэты proxy на серверах пласта доступу, і ўсе нашы працэсы, якія абслугоўваюць websocket api, падпішуцца на яго, а не на зыходную pub-sub кропку абмену ў ядры. Proxy падпісваецца на ядро ​​толькі ў выпадку ўнікальнай падпіскі і рэплікуе паступіўшае паведамленне па ўсіх сваіх падпісчыкам.
У выніку паміж ядром і серверамі доступу будзе пераслана 5 паведамленняў, замест 50000.

Маршрутызацыя і балансіроўка

Req-Resp

У бягучай рэалізацыі messaging існуе 7 стратэгій размеркавання запытаў:

  • default. Запыт перадаецца ўсім кантролерам.
  • round-robin. Ажыццяўляецца перабор і цыклічнае размеркаванне запытаў паміж кантролерамі.
  • consensus. Кантралёры, якія абслугоўваюць сэрвіс, дзеляцца на лідэра і кіраваных. Запыты перадаюцца толькі лідэру.
  • consensus & round-robin. У групе ёсць лідэр, але запыты размяркоўваюцца паміж усімі чальцамі.
  • sticky. Вылічаецца hash функцыя і замацоўваецца за пэўным апрацоўшчыкам. Наступныя запыты з гэтай сігнатурай трапляюць да гэтага ж апрацоўшчыка.
  • sticky-fun. Пры ініцыялізацыі кропкі абмену дадаткова перадаецца функцыя вылічэння хэша для sticky балансавання.
  • fun. Аналагічны sticky-fun, толькі дадаткова можна пераадрасаваць, адхіліць ці прадапрацаваць яго.

Стратэгія размеркавання задаецца пры ініцыялізацыі кропкі абмену.

Акрамя балансавання messaging дазваляе тэгаваць сутнасці. Разгледзім віды тэгаў у сістэме:

  • Тэг падключэння. Дазваляе зразумець, праз якое падключэнне прыйшлі падзеі. Выкарыстоўваецца, калі працэс кантролера падключаецца да адной кропцы абмену, але з рознымі ключамі маршрутызацыі.
  • Тэг сэрвісу. Дазваляе для аднаго сэрвісу аб'ядноўваць у групы апрацоўшчыкі і пашыраць магчымасці маршрутызацыі і балансавання. Для req-resp патэрна маршрутызацыя лінейная. Мы адпраўляем запыт на пункт абмену, далей ён перадае яго сэрвісу. Але калі нам трэба разбіць апрацоўшчыкі на лагічныя групы, то разбіццё ажыццяўляецца з дапамогай тэгаў. Пры ўказанні тэга, запыт будзе накіраваны на пэўную групу кантролераў.
  • Тэг запыту. Дазваляе адрозніваць адказы. Так як наша сістэма асінхронная, то для апрацоўкі адказаў сэрвісу трэба мець магчымасць указаць RequestTag пры адпраўцы запыту. Па ім мы зможам зразумець, адказ на які запыт да нас прыйшоў.

Pub-sub

Для pub-sub усё крыху прасцей. Мы маем кропку абмену на якую публікуюцца паведамленні. Кропка абмену размяркоўвае паведамленні паміж падпісчыкамі, якія падпісаліся на патрэбныя ім ключы маршрутызацыі (можна сказаць, што гэта аналаг тэм).

Маштабаванасць і адмоваўстойлівасць

Маштабаванасць сістэмы ў цэлым залежыць ад ступені маштабаванасці пластоў і кампанентаў сістэмы:

  • Сэрвісы маштабуюцца шляхам дадання ў кластар дадатковых вузлоў з апрацоўшчыкамі гэтага сэрвісу. У працэсе доследнай эксплуатацыі можна выбраць аптымальную палітыку балансавання.
  • Сам жа сэрвіс messaging у рамках асобнага кластара ў агульным выпадку маштабуецца альбо шляхам вынасу асоба нагружаных кропак абмену на асобныя вузлы кластара, альбо даданнем proxy працэсаў у асоба нагружаныя зоны кластара.
  • Маштабаванасць усёй сістэмы як характарыстыка залежыць ад гнуткасці архітэктуры і магчымасці аб'яднання асобных кластараў у агульную лагічную сутнасць.

Ад прастаты і хуткасці маштабавання часта залежыць паспяховасць праекту. Messaging у бягучым выкананні расце разам з дадаткам. Нават калі нам бракуе кластара ў 50-60 машын, можна звярнуцца да федэрацыі. Нажаль, тэма федэравання выходзіць за рамкі дадзенага артыкула.

Рэзерваванне

Пры разборы балансавання нагрузкі мы ўжо абмяркоўвалі рэзерваванне кантролераў сэрвісаў. Аднак messaging таксама павінен быць зарэзерваваны. У выпадку падзення вузла ці машыны, messaging павінен аўтаматычна аднавіцца, прычым у найкароткія тэрміны.

У сваіх праектах я выкарыстоўваю дадатковыя вузлы, якія падхапляюць нагрузку ў выпадку падзення. У Erlang існуе стандартная рэалізацыя размеркаванага рэжыму для прыкладанняў OTP. Distributed mode, як раз і ажыццяўляе аднаўленне ў выпадку збою шляхам запуску які ўпаў прыкладанні на іншым папярэдне запушчаным вузле. Працэс празрысты, пасля збою прыкладанне пераязджае аўтаматычна на failover вузел. Пачытаць пра гэты функцыянал падрабязней можна тут.

Proizvoditelnost

Паспрабуем хаця б прыблізна параўнаць прадукцыйнасць rabbitmq і нашага кастамнага messaging.
Я знайшоў афіцыйныя вынікі тэсціравання rabbitmq ад каманды openstack.

У пункце 6.14.1.2.1.2.2. арыгінальнага дакумента прадстаўлены вынік RPC CAST:
Будаўнічыя блокі размеркаваных прыкладанняў. Другое набліжэнне

Папярэдне ніякіх дадатковых налад у ядро ​​АС ці erlang VM уносіць не будзем. Умовы для тэсціравання:

  • erl opts: +A1 +sbtu.
  • Тэст у рамках аднаго вузла erlang запускаецца на наўтбуку са старэнькім i7 у мабільным выкананні.
  • Кластарныя выпрабаванні праходзяць на серверах з 10G сеткай.
  • Код працуе ў docker кантэйнерах. Сетка ў рэжыме NAT.

Код тэсту:

req_resp_bench(_) ->
  W = perftest:comprehensive(10000,
    fun() ->
      messaging:request(?EXCHANGE, default, ping, self()),
      receive
        #'$msg'{message = pong} -> ok
      after 5000 ->
        throw(timeout)
      end
    end
  ),
  true = lists:any(fun(E) -> E >= 30000 end, W),
  ok.

Сцэнар 1: Тэст запускаецца на ноўтбуку са старэнькім i7 мабільнага выканання. Тэст, messaging і сэрвіс выконваюцца на адным вузле ў адным docker-кантэйнеры:

Sequential 10000 cycles in ~0 seconds (26987 cycles/s)
Sequential 20000 cycles in ~1 seconds (26915 cycles/s)
Sequential 100000 cycles in ~4 seconds (26957 cycles/s)
Parallel 2 100000 cycles in ~2 seconds (44240 cycles/s)
Parallel 4 100000 cycles in ~2 seconds (53459 cycles/s)
Parallel 10 100000 cycles in ~2 seconds (52283 cycles/s)
Parallel 100 100000 cycles in ~3 seconds (49317 cycles/s)

Сцэнар 2: 3 вузла запушчаныя на розных машынах пад docker (NAT).

Sequential 10000 cycles in ~1 seconds (8684 cycles/s)
Sequential 20000 cycles in ~2 seconds (8424 cycles/s)
Sequential 100000 cycles in ~12 seconds (8655 cycles/s)
Parallel 2 100000 cycles in ~7 seconds (15160 cycles/s)
Parallel 4 100000 cycles in ~5 seconds (19133 cycles/s)
Parallel 10 100000 cycles in ~4 seconds (24399 cycles/s)
Parallel 100 100000 cycles in ~3 seconds (34517 cycles/s)

Ва ўсіх выпадках утылізацыя CPU не перавышала 250%

Вынікі

Спадзяюся, дадзены цыкл не выглядае, як дамп свядомасці і мой досвед прынясе рэальную карысць як даследнікам размеркаваных сістэм, так і практыкам, якія знаходзяцца ў самым пачатку шляху пабудовы размеркаваных архітэктур для сваіх бізнэс-сістэм і з цікавасцю глядзяць на Erlang/Elixir, але сумняваюцца ці варта…

Фота @chuttersnap

Толькі зарэгістраваныя карыстачы могуць удзельнічаць у апытанні. Увайдзіце, Калі ласка.

Якія тэмы мне варта асвятліць найбольш падрабязна ў рамках цыкла "Эксперымент VTrade"?

  • Тэорыя: Рынкі, ордэры і час іх дзеяння: DAY, GTD, GTC, IOC, FOK, MOO, MOC, LOO, LOC

  • Кніга ордэраў. Тэорыя і практыка рэалізацыі кнігі з групоўкамі

  • Візуалізацыя таргоў: Цікі, бары, рэзалюцыі. Як захоўваць і як ляпіць

  • Бэкафіс. Планаванне і распрацоўка. Кантроль супрацоўнікаў і расследаванне інцыдэнтаў

  • API. Разбіраемся якія інтэрфейсы патрэбны і як іх рэалізаваць

  • Захоўванне інфармацыі: PostgreSQL, Timescale, Tarantool у гандлёвых сістэмах

  • Рэактыўнасць у гандлёвых сістэмах

  • Іншае. Напішу ў каментарах

Прагаласавалі 6 карыстальнікаў. Устрымаліся 4 карыстальніка.

Крыніца: habr.com

Дадаць каментар