FAQ па архітэктуры і рабоце Укантакце

Гісторыя стварэння Укантакце ёсць у Вікіпедыі, яе расказваў сам Павел. Здаецца, яе ведаюць ужо ўсе. Пра вантробы, архітэктуру і прыладу сайта на HighLoad++ Павел расказваў яшчэ ў 2010 годзе. Шмат сервераў выцекла з тых часоў, таму мы абновім інфармацыю: прэпаруем, выцягнем вантробы, узважым - паглядзім на прыладу ВК з тэхнічнага пункта гледжання.

FAQ па архітэктуры і рабоце Укантакце

Аляксей Акуловіч (AterCattus) бэкэнд-распрацоўшчык у камандзе ВКонтакте. Расшыфроўка гэтага дакладу - зборны адказ на часта задаюць пытанні пра працу платформы, інфраструктуры, сервераў і ўзаемадзеянні паміж імі, але не пра распрацоўку, а менавіта пра жалеза. Асобна - пра базы дадзеных і тое, што замест іх у ВК, пра збор логаў і маніторынг усяго праекта ў цэлым. Падрабязнасці пад катом.



Больш за чатыры гады я займаюся разнастайнымі задачамі, звязанымі з бэкенд.

  • Загрузка, захоўванне, апрацоўка, раздача медыя: відэа, live стрымінг, аўдыё, фота, дакументы.
  • Інфраструктура, платформа, маніторынг з боку распрацоўніка, логі, рэгіянальныя кэшы, CDN, уласны пратакол RPC.
  • Інтэграцыя з вонкавымі сэрвісамі: рассыланні пушай, парсінг вонкавых спасылак, стужка RSS.
  • Дапамога калегам па розных пытаннях, за адказамі на якія даводзіцца апускацца ў невядомы код.

За гэты час я прыклаў руку да шматлікіх кампанентаў сайта. Гэтым досведам і жадаю падзяліцца.

Агульная архітэктура

Усё, як звычайна, пачынаецца з сервера ці групы сервераў, якія прымаюць запыты.

Front-сервер

Front-сервер прымае запыты па HTTPS, RTMP і WSS.

HTTPS - гэта запыты для асноўнай і мабільнай вэб-версій сайта: vk.com і m.vk.com, і іншыя афіцыйныя і неафіцыйныя кліенты нашага API: мабільныя кліенты, месэнджэры. У нас ёсць прыём RTMP-трафіку для Live-трансляцый з асобнымі front-серверамі і WSS-злучэнні для Streaming API.

Для HTTPS і WSS на серверах стаіць Nginx. Для RTMP трансляцый мы нядаўна перайшлі на ўласнае рашэнне kive, але яно за межамі даклада. Гэтыя серверы для адмоваўстойлівасці анансуюць агульныя IP-адрасы і выступаюць групамі, каб у выпадку праблемы на адным з сервераў, запыты карыстальнікаў не губляліся. Для HTTPS і WSS гэтыя ж серверы займаюцца шыфроўкай трафіку, каб забіраць частку нагрузкі па CPU на сябе.

Далей не будзем казаць пра WSS і RTMP, а толькі пра стандартныя запыты HTTPS, якія звычайна асацыююцца з вэб-праектам.

Backend

За front звычайна стаяць backend-серверы. Яны апрацоўваюць запыты, якія атрымлівае front-сервер ад кліентаў.

Гэта kPHP-серверы, На якіх працуе HTTP-дэман, таму што HTTPS ужо расшыфраваны. kPHP - гэта сервер, які працуе па prefork-мадэлі: запускае майстар-працэс, пачак даччыных працэсаў, перадае ім слухальнікі і яны апрацоўваюць свае запыты. Пры гэтым працэсы не перазапускаюцца паміж кожным запытам ад карыстача, а проста скідаюць свой стан у першапачатковы zero-value стан - запыт за запытам, замест перазапуску.

Размеркаванне нагрузкі

Усе нашы бэкэнды - гэта не велізарны пул машын, якія могуць апрацаваць любы запыт. Мы іх падзяляем на асобныя групы: general, mobile, api, video, staging… Праблема на асобнай групе машын не паўплывае на ўсе астатнія. У выпадку праблем з відэа карыстальнік, які слухае музыку, нават не даведаецца аб праблемах. На які backend адправіць запыт, вырашае nginx на front'е па канфігу.

Збор метрык і перабалансіроўка

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

Такіх сервераў у нас тысячы. На кожным фізічным серверы запушчана група kPHP, каб утылізаваць усе ядры (таму што kPHP аднаструменныя).

Сервер кантэнту

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

У нас дзясяткі тысяч фізічных сервераў, якія захоўваюць файлы. Карыстальнікі любяць загружаць файлы, а мы любім іх захоўваць і раздаваць. Частка гэтых сервераў зачыненыя адмысловымі pu/pp серверамі.

pu/pp

Калі вы адчынялі ў VK укладку network, то бачылі pu/pp.

FAQ па архітэктуры і рабоце Укантакце

Што такое pu/pp? Калі мы закрываем адзін сервер за іншы, то ёсць два варыянты аддачы і загрузкі файла на сервер, які быў зачынены: напрамую праз http://cs100500.userapi.com/path або праз прамежкавы сервер - http://pu.vk.com/c100500/path.

Pu - гэта гістарычна склаўшаяся назва для photo upload, а pp - гэта photo proxy. Гэта значыць адзін сервер, каб загружаць фота, а іншы - аддаваць. Цяпер загружаюцца ўжо не толькі фатаграфіі, але назва захавалася.

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

Бо машыны зачыненыя іншымі нашымі машынамі, то мы можам дазволіць сабе не даваць ім "белыя" вонкавыя IP, і даем «шэрыя». Так зэканомілі на пуле IP і гарантавана абаранілі машыны ад доступу звонку - проста няма IP, каб на яе патрапіць.

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

Спрэчны момант у тым, што ў гэтым выпадку кліент трымае менш злучэнняў. Пры наяўнасці аднолькавага IP на некалькі машын - з аднолькавым іх хастом: pu.vk.com або pp.vk.com, браўзэр кліента мае абмежаванне на колькасць адначасовых запытаў да аднаго хасту. Але падчас паўсюднага HTTP/2, я лічу, што гэта ўжо ня так актуальна.

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

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

Сонца

У верасні 2017 кампанія Oracle, якая да гэтага купіла кампанію Sun, звольніла велізарную колькасць супрацоўнікаў Sun. Можна сказаць, што ў гэты момант кампанія спыніла сваё існаванне. Выбіраючы назву для новай сістэмы, нашы адміны вырашылі аддаць даніну павагі і памяці гэтай кампаніі, і назвалі новую сістэму Sun. Паміж сабой мы называем яе проста "сонейкі".

FAQ па архітэктуры і рабоце Укантакце

У pp было некалькі праблем. Адзін IP на групу - неэфектыўны кэш. Некалькі фізічных сервераў маюць агульны IP-адрас, і няма магчымасці кантраляваць, на які сервер прыйдзе запыт. Таму калі розныя карыстачы прыходзяць за адным і тым жа файлам, то пры наяўнасці кэша на гэтых серверах, файлік асядае ў кэшы кожнага сервера. Гэта вельмі неэфектыўная схема, але нічога нельга было зрабіць.

Як следства - мы не можам шаляваць кантэнт, таму што не можам абраць пэўны сервер гэтай групы - у іх агульны IP. Таксама па некаторых унутраных прычынах у нас не было магчымасці ставіць такія серверы ў рэгіёны. Яны стаялі толькі ў Санкт-Пецярбургу.

З сонейкамі мы змянілі сістэму выбару. Цяпер у нас anycast маршрутызацыя: dynamic routing, anycast, self-check daemon. У кожнага сервера свой уласны індывідуальны IP, але пры гэтым агульная падсетка. Усё наладжана так, што ў выпадку выпадзення аднаго сервера трафік размазваецца па астатніх серверах той жа групы аўтаматычна. Цяпер ёсць магчымасць абраць пэўны сервер, няма залішняга кэшавання, і надзейнасць не пацярпела.

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

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

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

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

Sun знутры

Рэверс проксі на nginx, кэш альбо ў RAM, альбо на хуткія дыскі Optane/NVMe. Прыклад: http://sun4-2.userapi.com/c100500/path - спасылка на "сонейка", якое стаіць у чацвёртым рэгіёне, другі сервер-групы. Ён зачыняе сабой файл path, які фізічна ляжыць на сэрвэры 100500.

Кэш

У нашу архітэктурную схему мы дадаем яшчэ адзін вузел - асяроддзе кэшавання.

FAQ па архітэктуры і рабоце Укантакце

Ніжэй схема размяшчэння рэгіянальных кэшаў, іх прыкладна 20 штук. Гэта месцы, дзе стаяць менавіта кэшы і "сонейкі", якія могуць кэшаваць трафік праз сябе.

FAQ па архітэктуры і рабоце Укантакце

Гэта кэшаванне мультымедыя-кантэнту, тут не захоўваюцца карыстацкія дадзеныя - проста музыка, відэа, фота.

Каб вызначыць рэгіён карыстальніка, мы збіраем анансаваныя ў рэгіёнах BGP-прэфіксы сетак. У выпадку fallback у нас ёсць яшчэ парсінг базы geoip, калі мы не змаглі знайсці IP па прэфіксах. Па IP карыстальніка вызначаем рэгіён. У кодзе мы можам паглядзець адзін або некалькі рэгіёнаў карыстальніка - тыя кропкі, да якіх ён бліжэй за ўсё геаграфічна.

Як гэта працуе?

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

Пры гэтым дэманы – сэрвісы ў рэгіёнах – час ад часу прыходзяць у API і кажуць: «Я кэш вось такой, дай мне спіс самых папулярных файлаў майго рэгіёна, якіх на мне яшчэ няма». API аддае пачак файлаў, адсартаваных па рэйтынгу, дэман іх выпампоўвае, выносіць у рэгіёны і адтуль аддае файлы. Гэта прынцыповае адрозненне pu/pp і Sun ад кэшаў: тыя аддаюць файл праз сябе адразу, нават калі ў кэшы гэтага файла няма, а кэш спачатку выпампоўвае файл на сябе, а потым ужо пачынае яго аддаваць.

Пры гэтым мы атрымліваем кантэнт бліжэй да карыстальнікаў і размазванне сеткавай нагрузкі. Напрыклад, толькі з маскоўскага кэша мы раздаём больш за 1 Тбіт/с у гадзіны найбольшай нагрузкі.

Але ёсць праблемы - серверы кэшаў не гумовыя. Для суперпапулярнага кантэнту часам бракуе сеткі на асобны сервер. Серверы кэшаў у нас 40-50 Гбіт/з, але бывае кантэнт, які забівае такі канал цалкам. Мы ідзем да таго, каб рэалізаваць захоўванне больш за адну копіі папулярных файлаў у рэгіёне. Спадзяюся, што да канца года рэалізуемы.

Мы разгледзелі агульную архітэктуру.

  • Front-серверы, якія прымаюць запыты.
  • Бэкэнды, якія апрацоўваюць запыты.
  • Сховішчы, якія зачыненыя двума відамі proxy.
  • Рэгіянальныя кэшы.

Чаго ў гэтай схеме не хапае? Вядома, базы дадзеных, у якіх мы захоўваем дадзеныя.

Базы дадзеных або рухавічкі

Мы называем іх не базамі, а рухавікамі – Engines, таму што баз дадзеных у агульнапрынятым сэнсе ў нас практычна няма.

FAQ па архітэктуры і рабоце Укантакце

Гэта змушаная мера. Так атрымалася, таму што ў 2008-2009 годзе, калі ў VK быў выбухны рост папулярнасці, праект поўнасцю працаваў на MySQL і Memcache і былі праблемы. MySQL любіў зваліцца і сапсаваць файлы, пасля чаго не паднімаўся, а Memcache паступова дэградаваў па прадукцыйнасці, і даводзілася яго перазапускаць.

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

Рашэнне аказалася паспяховым. Магчымасць гэта зрабіць была, як і скрайняя неабходнасць, таму што іншых спосабаў маштабавання ў той час не існавала. Не было кучы баз, NoSQL яшчэ не існаваў, былі толькі MySQL, Memcache, PostrgreSQL - і ўсё.

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

Тыпы рухавікоў

Каманда напісала даволі шмат рухавікоў. Вось толькі частка з іх: friend, hints, image, ipdb, letters, lists, logs, memcached, meowdb, news, nostradamus, фота, playlists, pmemcached, sandbox, search, storage, likes, tasks, …

Пад кожную задачу, якая патрабуе спецыфічную структуру дадзеных ці апрацоўвае нетыповыя запыты, C-каманда піша новы рухавічок. Чаму б і не.

У нас ёсць асобны рухавічок memcached, Які падобны на звычайны, але з кучай плюшак, і які не тармозіць. Ня ClickHouse, але таксама працуе. Ёсць асобна pmemcached - Гэта персістэнтны memcached, які ўмее захоўваць дадзеныя яшчэ і на дыску, прычым больш, чым залазіць у аператыўную памяць, каб не губляць дадзеныя пры перазапуску. Ёсць разнастайныя рухавічкі пад асобныя задачы: чэргі, спісы, сэты - усё, што патрабуецца нашаму праекту.

кластары

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

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

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

RPC-proxy

Proxy - злучная шына, на якой працуе практычна ўвесь сайт. Пры гэтым у нас няма service discovery - замест яго ёсць канфіг гэтага proxy, які ведае размяшчэнне ўсіх кластараў і ўсіх шардаў гэтага кластара. Гэтым займаюцца адміны.

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

FAQ па архітэктуры і рабоце Укантакце

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

Спецыфічныя рэалізацыі

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

Для MySQL, які ў нас яшчэ дзе-то ёсць выкарыстоўваем db-proxy, а для ClickHouse - Kittenhouse.

Гэта працуе ў цэлым так. Ёсць нейкі сервер, на ім працуе kPHP, Go, Python - наогул любы код, які ўмее хадзіць па нашым RPC-пратаколу. Код ходзіць лакальна на RPC-proxy - на кожным серверы, дзе ёсць код, запушчаны свой лакальны proxy. Па запыце proxy разумее куды трэба ісці.

FAQ па архітэктуры і рабоце Укантакце

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

Прыклад TL-схемы, па якой працуюць усе рухавічкі.

memcache.not_found                                = memcache.Value;
memcache.strvalue	value:string flags:int = memcache.Value;
memcache.addOrIncr key:string flags:int delay:int value:long = memcache.Value;

tasks.task
    fields_mask:#
    flags:int
    tag:%(Vector int)
    data:string
    id:fields_mask.0?long
    retries:fields_mask.1?int
    scheduled_time:fields_mask.2?int
    deadline:fields_mask.3?int
    = tasks.Task;
 
tasks.addTask type_name:string queue_id:%(Vector int) task:%tasks.Task = Long;

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

RPC праз TL праз TCP/UDP… UDP?

У нас ёсць RPC-пратакол выканання запытаў рухавічка, які працуе па-над TL-схемы. Гэта ўсё працуе па-над TCP/UDP злучэння. TCP – зразумела, а навошта нам UDP часта пытаюцца.

UDP дапамагае пазбегнуць праблемы вялікай колькасці злучэнняў паміж серверамі. Калі на кожным серверы варта RPC-proxy і ён, у агульным выпадку, можа пайсці ў любы рухавічок, тое атрымліваецца дзясяткі тысяч TCP-злучэнняў на сервер. Нагрузка ёсць, але яна бескарысная. У выпадку UDP гэтай праблемы няма.

He залішняга TCP-handshake. Гэта тыповая праблема: калі паднімаецца новы рухавічок ці новы сервер, усталёўваецца адразу шмат TCP-злучэнняў. Для маленькіх легкаважных запытаў, напрыклад, UDP payload, усе зносіны кода з рухавіком – гэта два UDP-пакета: адзін ляціць у адзін бок, другі ў іншы. Адзін round trip - і код атрымаў адказ ад рухавічка без handshake.

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

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

Персістэнтнае захоўванне дадзеных

Рухавікі пішуць бінлогі. Бінлог - гэта файлік, у канцы якога дапісваецца падзея на змену стану або дадзеных. У розных рашэннях называецца па-рознаму: binary log, WAL, AOF, Але прынцып адзін.

Каб рухавічок пры перазапуску не перачытваў увесь бінглог за шмат гадоў, рухавічкі пішуць. снапшоты - стан на бягучы момант. Пры неабходнасці яны чытаюць спачатку з яго, а потым дачытваюць ужо з бінгла. Усе бінглогі пішуцца ў аднолькавым бінарным фармаце – па TL-схеме, каб адміны маглі іх сваімі інструментамі аднолькава адміністраваць. Для снапшотаў такой неабходнасці няма. Там ёсць агульны загаловак, які паказвае чый снапшот - int, magic рухавічка, а якое цела - нікому не важна. Гэта праблема рухавічка, які запісаў снапшот.

Бегла апішу прынцып працы. Ёсць сервер, на якім працуе рухавічок. Ён адчыняе на запіс новы пусты бінглог, піша ў яго падзея на змену.

FAQ па архітэктуры і рабоце Укантакце

У нейкі момант ён альбо сам вырашае зрабіць снапшот, альбо яму прыходзіць сігнал. Сервер стварае новы файл, цалкам запісвае ў яго свой стан, дапісвае бягучы памер бінга - offset на канец файла, і працягвае пісаць далей. Новы бінг не ствараецца.

FAQ па архітэктуры і рабоце Укантакце

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

FAQ па архітэктуры і рабоце Укантакце

Вычытвае пазіцыю, якая была на момант стварэння снапшота, і памер бінгла.

FAQ па архітэктуры і рабоце Укантакце

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

Рэплікацыя дадзеных

У выніку рэплікацыя дадзеных у нас statement-based - Мы пішам у бінглог не якія-небудзь змены старонак, а менавіта запыты на змены. Вельмі падобна на тое, што прыходзіць па сетцы, толькі крыху змененае.

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

FAQ па архітэктуры і рабоце Укантакце

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

Адставанне тут вельмі маленькае, і ёсць магчымасць даведацца, наколькі рэпліка адстае ад майстра.

Шардзіраванне дадзеных у RPC-proxy

Як працуе шакіраванне? Як proxy разумее, на які шард кластара адправіць? Код не паведамляе: «Адпраў на 15 шард!» - не, гэта робіць проксі.

Самая простая схема - firstint - Першы лік у запыце.

get(photo100_500) => 100 % N.

Гэта прыклад для простага memcached тэкставага пратакола, але, вядома, запыты бываюць складаныя, структураваныя. У прыкладзе бярэцца першы лік у запыце і рэшта ад дзялення на памер кластара.

Гэта карысна, калі мы жадаем мець лакальнасьць дадзеных адной сутнасьці. Дапусцім, 100 – карыстач або ID групы, і мы жадаем, каб для складаных запытаў усе дадзеныя адной сутнасці былі на адным шардзе.

Калі нам усё роўна, як запыты размазаныя па кластары, ёсць іншы варыянт - хэшаванне шарда цалкам.

hash(photo100_500) => 3539886280 % N

Таксама атрымліваем хэш, рэшту ад дзялення і нумар шарда.

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

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

Ёсць супер-спецыфічныя запыты. Гэта выглядае так: RPC-proxy атрымлівае запыт, вызначае, у які кластар пайсці і вызначае шард. Тады ёсць альбо якія пішуць майстры, альбо, калі кластар мае падтрымку рэплік, ён адсылае ў рэпліку па запыце. Гэтым усім займаецца проксі.

FAQ па архітэктуры і рабоце Укантакце

Логі

Мы пішам логі некалькімі спосабамі. Самы відавочны і просты - пішам логі ў memcache.

ring-buffer: prefix.idx = line

Ёсць прэфікс ключа - імя лога, радок, і ёсць памер гэтага лога - колькасць радкоў. Бярэм выпадковы лік ад 0 да ліку радкоў мінус 1. Ключ у memcache - гэта прэфікс сканкатэніраваны з гэтым выпадковым лікам. У значэнне захоўваем радок лога і бягучы час.

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

Для надзейнага захоўвання логаў у нас ёсць рухавічок logs-engine. Менавіта для гэтага ён і ствараўся, і шырока выкарыстоўваецца ў вялікай колькасці кластараў. Самы вялікі вядомы мне кластар захоўвае 600 Тбайт запакаваных логаў.

Рухавічок вельмі стары, ёсць кластары, якім ужо па 6-7 гадоў. З ім ёсць праблемы, якія мы спрабуем вырашыць, напрыклад, пачалі актыўна выкарыстоўваць ClickHouse для захоўвання логаў.

Збор логаў у ClickHouse

Гэтая схема паказвае, як мы ходзім у нашы рухавічкі.

FAQ па архітэктуры і рабоце Укантакце

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

  • замяніць нейкі рухавічок на ClickHouse;
  • замяніць RPC-proxy, які не ўмее хадзіць у ClickHouse, на нейкае рашэнне, якое ўмее, прычым па RPC.

З рухавіком проста - заменны яго на сервер або на кластар сервераў з ClickHouse.

А каб хадзіць у ClickHouse, мы зрабілі KittenHouse. Калі мы пойдзем напрамую з KittenHouse у ClickHouse - ён не справіцца. Нават без запытаў, ад HTTP-злучэнняў вялікай колькасці машын ён складаецца. Каб схема працавала, на серверы з ClickHouse паднімаецца лакальны reverse proxy, які напісаны так, што вытрымлівае патрэбныя аб'ёмы злучэнняў. Таксама ён можа адносна надзейна буферызаваць дадзеныя ў сабе.

FAQ па архітэктуры і рабоце Укантакце

Часам мы не жадаем рэалізоўваць RPC-схему ў нестандартных рашэннях, напрыклад, у nginx. Таму ў KittenHouse ёсць магчымасць атрымліваць логі па UDP.

FAQ па архітэктуры і рабоце Укантакце

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

Маніторынг

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

Сістэмныя метрыкі

На ўсіх серверах у нас працуе netdata, якая збірае статыстыку і адсылае яе ў Graphite Carbon. Таму як сістэма захоўвання выкарыстоўваецца ClickHouse, а не Whisper, напрыклад. Пры неабходнасці можна напрамую чытаць з ClickHouse, або выкарыстоўваць Графана для метрык, графікаў і справаздач. Як распрацоўнікам, доступу да Netdata і Grafana нам хапае.

Прадуктовыя метрыкі

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

statlogsCountEvent   ( ‘stat_name’,            $key1, $key2, …)
statlogsUniqueCount ( ‘stat_name’, $uid,    $key1, $key2, …)
statlogsValuetEvent  ( ‘stat_name’, $value, $key1, $key2, …)

$stats = statlogsStatData($params)

Пасля мы можам выкарыстоўваць фільтры сартавання, групоўкі і зрабіць усё, што хочам ад статыстыкі - пабудаваць графікі, наладзіць Watсhdogs.

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

У нас ёсць функцыі, якія пішуць гэтыя метрыкі у лакальны memcache, каб паменшыць колькасць запісаў. Адзін раз у невялікі прамежак часу лакальна запушчаны stats-daemon збірае ўсе запісы. Далей дэман злівае метрыкі ў два пласта сервераў logs-collectors, якія агрэгуе статыстыку з кучы нашых машын, каб пласт за імі не паміраў.

FAQ па архітэктуры і рабоце Укантакце

Па неабходнасці мы можам пісаць напрамую ў logs-collectors.

FAQ па архітэктуры і рабоце Укантакце

Але запіс з кода напрамую ў калектары ў абыход stas-daemom - дрэнна маштабуецца рашэнне, таму што павялічвае нагрузку на collector. Рашэнне падыдзе, толькі калі па нейкай прычыне на машыне мы не можам падняць memcache stats-daemon, альбо ён упаў, і мы пайшлі напрамую.

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

FAQ па архітэктуры і рабоце Укантакце

Потым з кода можам бінарным "каля-SQL" вырабляць выбаркі.

FAQ па архітэктуры і рабоце Укантакце

Эксперымент

Улетку 2018 у нас быў унутраны хакатон, і з'явілася ідэя паспрабаваць замяніць чырвоную частку схемы на нешта, што можа захоўваць метрыкі ў ClickHouse. У нас ёсць логі на ClickHouse - чаму б не паспрабаваць?

FAQ па архітэктуры і рабоце Укантакце

У нас была схема, якая пісала логі праз KittenHouse.

FAQ па архітэктуры і рабоце Укантакце

Мы вырашылі дадаць у схему яшчэ адзін «House», які будзе прымаць менавіта метрыкі ў тым фармаце, як іх піша наш код па UDP. Далей гэты *House ператварае іх у inserts, як логі, якія разумее KittenHouse. Гэтыя логі ён умее выдатна дастаўляць да ClickHouse, які павінен іх умець чытаць.

FAQ па архітэктуры і рабоце Укантакце

Схема з memcache, stats-daemon і logs-collectors базы замяняецца на такую.

FAQ па архітэктуры і рабоце Укантакце

Схема з memcache, stats-daemon і logs-collectors базы замяняецца на такую.

  • Тут ёсць адпраўка з кода, якая лакальна пішацца ў StatsHouse.
  • StatsHouse піша ў KittenHouse UDP-метрыкі, ужо пераўтвораныя ў SQL-inserts, пачкамі.
  • KittenHouse адсылае іх у ClickHouse.
  • Калі мы жадаем іх прачытаць, то чытэльны ўжо ў абыход StatsHouse — напроста з ClickHouse звычайнымі SQL.

Гэта ўсё яшчэ эксперымент, але нам падабаецца, што атрымліваецца. Калі выправім праблемы схемы, то, магчыма, поўнасцю пяройдзем на яе. Асабіста я на гэта спадзяюся.

схема не эканоміць жалеза. Трэба менш сервераў, не патрэбныя лакальныя stats-daemons і logs-collectors, але ClickHouse патрабуе сервера сыцей, чым тыя, што стаяць на бягучай схеме. Сервераў трэба менш, але яны павінны быць даражэй і магутней.

Дэплой

Спачатку паглядзім на дэплой PHP. Распрацоўку вядзём у мярзотнік: выкарыстоўваем GitLab и TeamCity для дэплою. Галінкі распрацоўшчыкаў уліваюцца ў майстар-галінку, з майстра для тэставання ўліваюцца ў стэйджынг, з стэйджынгу - у прадакшн.

Перад дэплоем бяруцца бягучая галінка прадакшна і папярэдняя, ​​у іх лічыцца diff файлаў - змена: створаны, выдалены, зменены. Гэтая змена запісваецца ў binlog спецыяльнага рухавічка copyfast, які можа хутка рэплікаваць змены на ўвесь наш парк сервераў. Тут выкарыстоўваецца не капіраванне напрамую, а gossip replication, калі адзін сервер рассылае змены бліжэйшым суседзям, тыя - сваім суседзям, і гэтак далей. Гэта дазваляе абнаўляць код за дзясяткі і адзінкі секунд на ўсім парку. Калі змена даязджае да лакальнай рэплікі, яна прымяняе гэтыя патчы на ​​сваёй лакальнай файлавай сістэме. Па гэтай жа схеме робіцца і адкат.

Мы таксама шмат дэплоім kPHP і ў яго таксама ёсць уласная распрацоўка на мярзотнік па схеме вышэй. Бо гэта бінарнік HTTP-сервера, то мы не можам вырабляць diff - рэлізны бінарнік важыць сотні Мбайт. Таму тут варыянт іншы - версія запісваецца ў binlog copyfast. З кожным білдам яна інкрыментуецца, і пры адкаце яна таксама павялічваецца. Версія рэплікуецца на серверы. Лакальныя copyfast'ы бачаць, што ў binlog патрапіла новая версія, і тым жа самым gossip replication забіраюць сабе свежую версію бінарніка, не стамляючы наш майстар-сервер, а акуратна размазваючы нагрузку па сетцы. Далей варта graceful перазапуск на новую версію.

Для нашых рухавікоў, якія таксама на самай справе бінарнікі, схема вельмі падобная:

  • git master branch;
  • бінарнік у дэбютантка;
  • версія запісваецца ў binlog copyfast;
  • рэплікуецца на серверы;
  • сервер выцягвае сабе свежы .dep;
  • дпкг -і;
  • graceful перазапуск на новую версію.

Розніца ў тым, што бінарнік у нас запакоўваецца ў архівы. дэбютантка, і пры выпампоўванні яны дпкг -і ставяцца на сістэму. Чаму ў нас kPHP деплоится бінарнікам, а рухавічкі - dpkg? Так склалася. Працуе - не чапаем.

Карысныя спасылкі:

Аляксей Акуловіч адзін з тых, хто ў складзе Праграмнага камітэта дапамагае PHP Belarus ужо 17 траўня стаць самай маштабнай за апошні час падзеяй для PHP-распрацоўнікаў. Паглядзіце, які ў нас круты ПК, якія спікеры (двое з іх распрацоўваюць ядро ​​PHP!) - здаецца, што калі вы пішыце на PHP, гэта нельга прапусціць.

Крыніца: habr.com

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