Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

La historio de la kreado de VKontakte estas en Vikipedio; ĝi estis rakontita de Pavel mem. Ŝajnas, ke ĉiuj jam konas ŝin. Pri la internoj, arkitekturo kaj strukturo de la retejo sur HighLoad++ Pavel diris al mi jam en 2010. Multaj serviloj filtris ekde tiam, do ni ĝisdatigos la informojn: ni dissekcos ĝin, elprenos la internojn, pezos ĝin kaj rigardos la VK-aparaton el teknika vidpunkto.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Aleksej Akuloviĉ (AterCattus) backend-programisto en la VKontakte-teamo. La transskribo de ĉi tiu raporto estas kolektiva respondo al oftaj demandoj pri la funkciado de la platformo, infrastrukturo, serviloj kaj interago inter ili, sed ne pri evoluo, nome pri fero. Aparte, pri datumbazoj kaj kion VK havas anstataŭe, pri kolektado de protokoloj kaj monitorado de la tuta projekto entute. Detaloj sub la tranĉo.



Dum pli ol kvar jaroj mi okupiĝas pri ĉiaj taskoj rilataj al la backend.

  • Alŝuto, konservado, prilaborado, disdonado de amaskomunikiloj: filmetoj, rekta streaming, aŭdio, fotoj, dokumentoj.
  • Infrastrukturo, platformo, programisto-monitorado, protokoloj, regionaj kaŝmemoroj, CDN, proprieta RPC-protokolo.
  • Integriĝo kun eksteraj servoj: puŝaj sciigoj, analizo de eksteraj ligoj, RSS-fluo.
  • Helpi kolegojn kun diversaj demandoj, kies respondoj postulas plonĝi en nekonatan kodon.

Dum ĉi tiu tempo, mi havis manon en multaj komponantoj de la retejo. Mi volas dividi ĉi tiun sperton.

Ĝenerala arkitekturo

Ĉio, kiel kutime, komenciĝas per servilo aŭ grupo de serviloj, kiuj akceptas petojn.

Antaŭa servilo

La antaŭa servilo akceptas petojn per HTTPS, RTMP kaj WSS.

HTTPS - jen petoj por la ĉefaj kaj moveblaj TTT-versioj de la retejo: vk.com kaj m.vk.com, kaj aliaj oficialaj kaj neoficialaj klientoj de nia API: moveblaj klientoj, mesaĝistoj. Ni havas akcepton RTMP-trafiko por Vivaj elsendoj kun apartaj antaŭaj serviloj kaj WSS- konektoj por Streaming API.

Por HTTPS kaj WSS sur serviloj valoras nginx. Por RTMP-elsendoj, ni lastatempe ŝanĝis al nia propra solvo kive, sed ĝi estas ekster la amplekso de la raporto. Por misfunkciado, ĉi tiuj serviloj reklamas oftajn IP-adresojn kaj agas en grupoj, por ke se estas problemo sur unu el la serviloj, uzantpetoj ne perdiĝas. Por HTTPS kaj WSS, ĉi tiuj samaj serviloj ĉifras trafikon por preni parton de la CPU-ŝarĝo sur si mem.

Ni ne parolos plu pri WSS kaj RTMP, sed nur pri normaj HTTPS-petoj, kiuj kutime estas asociitaj kun retprojekto.

backend

Malantaŭ la fronto estas kutime backend serviloj. Ili procesas petojn, kiujn la antaŭa servilo ricevas de klientoj.

ĉi kPHP-serviloj, sur kiu la HTTP-demono funkcias, ĉar HTTPS jam estas deĉifrita. kPHP estas servilo, kiu funkcias prefork modeloj: komencas majstran procezon, aron da infanaj procezoj, pasas aŭskultantajn ingojn al ili kaj ili procesas siajn petojn. En ĉi tiu kazo, procezoj ne estas rekomencitaj inter ĉiu peto de la uzanto, sed simple restarigas sian staton al la origina nulvalora stato - peto post peto, anstataŭ rekomenci.

Ŝarĝa distribuo

Ĉiuj niaj backends ne estas grandega aro da maŝinoj, kiuj povas procesi ajnan peton. Ni ilin dividita en apartajn grupojn: ĝenerala, movebla, api, video, surscenigo... La problemo sur aparta grupo de maŝinoj ne influos ĉiujn aliajn. En kazo de problemoj kun video, la uzanto, kiu aŭskultas muzikon, eĉ ne scios pri la problemoj. Al kiu backend sendi la peton decidas nginx ĉe la fronto laŭ la agordo.

Metrika kolekto kaj rebalancado

Por kompreni kiom da aŭtoj ni bezonas havi en ĉiu grupo, ni ne fidu je QPS. La backends estas malsamaj, ili havas malsamajn petojn, ĉiu peto havas malsaman kompleksecon de kalkulado de QPS. Tial ni ni funkcias kun la koncepto de ŝarĝo sur la servilo kiel tuto - sur la CPU kaj perf.

Ni havas milojn da tiaj serviloj. Ĉiu fizika servilo prizorgas kPHP-grupon por recikli ĉiujn kernojn (ĉar kPHP estas unufadenigita).

Enhava Servilo

CS aŭ Content Server estas stokado. CS estas servilo, kiu stokas dosierojn kaj ankaŭ prilaboras alŝutitajn dosierojn kaj ĉiajn fonajn sinkronajn taskojn, kiujn la ĉefa interreta fasado asignas al ĝi.

Ni havas dekojn da miloj da fizikaj serviloj, kiuj stokas dosierojn. Uzantoj amas alŝuti dosierojn, kaj ni amas konservi kaj dividi ilin. Kelkaj el ĉi tiuj serviloj estas fermitaj de specialaj pu/pp-serviloj.

pu/pp

Se vi malfermis la retan langeton en VK, vi vidis pu/pp.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Kio estas pu/pp? Se ni fermas unu servilon post alia, tiam estas du ebloj por alŝuti kaj elŝuti dosieron al la fermita servilo: rekte tra http://cs100500.userapi.com/pathper meza servilo - http://pu.vk.com/c100500/path.

Pu estas la historia nomo por foto-alŝuto, kaj pp estas foto-prokuro. Tio estas, unu servilo estas por alŝuto de fotoj, kaj alia estas por alŝuto. Nun ne nur fotoj estas ŝarĝitaj, sed la nomo estas konservita.

Ĉi tiuj serviloj ĉesigi HTTPS-sesiojnforigi la procesoran ŝarĝon de la stokado. Ankaŭ, ĉar uzantdosieroj estas prilaboritaj sur ĉi tiuj serviloj, ju malpli sentema informo stokita sur ĉi tiuj maŝinoj, des pli bone. Ekzemple, HTTPS-ĉifradaj ŝlosiloj.

Ĉar la maŝinoj estas fermitaj de niaj aliaj maŝinoj, ni povas permesi ne doni al ili "blankajn" eksterajn IP-ojn, kaj donu "grizan". Tiel ni ŝparis sur la IP-pool kaj garantiis protekti la maŝinojn kontraŭ ekstera aliro - simple ne ekzistas IP por eniri ĝin.

Fortikeco super komunaj IP-oj. Rilate al misfunkciado, la skemo funkcias same - pluraj fizikaj serviloj havas komunan fizikan IP, kaj la aparataro antaŭ ili elektas kien sendi la peton. Mi parolos pri aliaj ebloj poste.

La polemika punkto estas tio en ĉi tiu kazo la kliento konservas malpli da konektoj. Se ekzistas la sama IP por pluraj maŝinoj - kun la sama gastiganto: pu.vk.com aŭ pp.vk.com, la klienta retumilo havas limon pri la nombro da samtempaj petoj al unu gastiganto. Sed en la tempo de ĉiea HTTP/2, mi kredas, ke tio ne plu estas tiel grava.

La evidenta malavantaĝo de la skemo estas, ke ĝi devas pumpi la tutan trafikon, kiu iras al la stokado, per alia servilo. Ĉar ni pumpas trafikon tra maŝinoj, ni ankoraŭ ne povas pumpi pezan trafikon, ekzemple, video, uzante la saman skemon. Ni transsendas ĝin rekte - aparta rekta konekto por apartaj stokaĵoj specife por video. Ni transdonas pli malpezan enhavon per prokurilo.

Antaŭ nelonge ni ricevis plibonigitan version de prokurilo. Nun mi rakontos al vi kiel ili diferencas de ordinaraj kaj kial ĉi tio estas necesa.

suno

En septembro 2017, Oracle, kiu antaŭe aĉetis Sunon, maldungis grandegan nombron da Sun-dungitoj. Ni povas diri, ke ĉi-momente la kompanio ĉesis ekzisti. Elektinte nomon por la nova sistemo, niaj administrantoj decidis omaĝi la memoron de ĉi tiu kompanio kaj nomis la novan sistemon Suno. Inter ni ni simple nomas ŝin "sunoj".

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

pp havis kelkajn problemojn. Unu IP por grupo - neefika kaŝmemoro. Pluraj fizikaj serviloj dividas komunan IP-adreson, kaj ne ekzistas maniero kontroli al kiu servilo iros la peto. Tial, se malsamaj uzantoj venas por la sama dosiero, tiam se estas kaŝmemoro sur ĉi tiuj serviloj, la dosiero finiĝas en la kaŝmemoro de ĉiu servilo. Ĉi tio estas tre malefika skemo, sed nenio povus esti farita.

Sekve - ni ne povas dividi enhavon, ĉar ni ne povas elekti specifan servilon por ĉi tiu grupo - ili havas komunan IP. Ankaŭ pro iuj internaj kialoj ni havas ne eblis instali tiajn servilojn en regionoj. Ili staris nur en Peterburgo.

Kun la sunoj, ni ŝanĝis la elektosistemon. Nun ni havas anycast-vojigo: dinamika vojigo, anycast, memkontrola demono. Ĉiu servilo havas sian propran individuan IP, sed komunan subreton. Ĉio estas agordita tiel, ke se unu servilo malsukcesas, la trafiko disvastiĝas tra la aliaj serviloj de la sama grupo aŭtomate. Nun eblas elekti specifan servilon, neniu redunda kaŝmemoro, kaj fidindeco ne estis tuŝita.

Subteno de pezo. Nun ni povas pagi instali maŝinojn de malsama potenco laŭbezone, kaj ankaŭ, en kazo de provizoraj problemoj, ŝanĝi la pezojn de la laborantaj "sunoj" por redukti la ŝarĝon sur ili, por ke ili "ripozu" kaj eklaboru denove.

Interdivido per enhavo-identigilo. Amuza afero pri sharding: ni kutime disdividas enhavon tiel ke malsamaj uzantoj iru al la sama dosiero tra la sama "suno" por ke ili havu komunan kaŝmemoron.

Ni lastatempe lanĉis la aplikaĵon "Clover". Ĉi tio estas interreta kvizo en viva elsendo, kie la gastiganto faras demandojn kaj uzantoj respondas en reala tempo, elektante eblojn. La programo havas babilejon, kie uzantoj povas babili. Povas samtempe konektiĝi al la elsendo pli ol 100 mil homoj. Ili ĉiuj skribas mesaĝojn kiuj estas senditaj al ĉiuj partoprenantoj, kaj avataro venas kune kun la mesaĝo. Se 100 mil homoj venas por unu avataro en unu "suno", tiam ĝi foje povas ruliĝi malantaŭ nubo.

Por rezisti eksplodojn de petoj por la sama dosiero, estas por certa speco de enhavo, ke ni ŝaltas stultan skemon, kiu disvastigas dosierojn tra ĉiuj disponeblaj "sunoj" en la regiono.

Suno de interne

Inversa prokurilo sur nginx, kaŝmemoru aŭ en RAM aŭ sur rapidaj Optane/NVMe-diskoj. Ekzemplo: http://sun4-2.userapi.com/c100500/path — ligilo al la "suno", kiu situas en la kvara regiono, la dua servila grupo. Ĝi fermas la padodosieron, kiu fizike kuŝas sur la servilo 100500.

kovrilo

Ni aldonas unu plian nodon al nia arkitektura skemo - la kaŝmemoro.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Malsupre estas la aranĝa diagramo regionaj kaŝmemoroj, estas ĉirkaŭ 20 el ili. Ĉi tiuj estas la lokoj, kie troviĝas kaŝmemoroj kaj "sunoj", kiuj povas konservi trafikon per si mem.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Ĉi tio estas konservado de plurmedia enhavo; neniuj uzantdatenoj estas stokitaj ĉi tie - nur muziko, video, fotoj.

Por determini la regionon de la uzanto, ni ni kolektas BGP-retajn prefiksojn anoncitajn en la regionoj. En la kazo de falo, ni ankaŭ devas analizi la geoip-datumbazon se ni ne povus trovi la IP per prefiksoj. Ni determinas la regionon per la IP de la uzanto. En la kodo, ni povas rigardi unu aŭ plurajn regionojn de la uzanto - tiujn punktojn al kiuj li geografie estas plej proksima.

Kiel ĝi funkcias?

Ni kalkulas la popularecon de dosieroj laŭ regiono. Estas kelkaj el la regiona kaŝmemoro kie la uzanto situas, kaj dosieridentigilo - ni prenas ĉi tiun paron kaj pliigas la takson kun ĉiu elŝuto.

Samtempe, demonoj - servoj en regionoj - de tempo al tempo venas al la API kaj diras: "Mi estas tia kaj tia kaŝmemoro, donu al mi liston de la plej popularaj dosieroj en mia regiono, kiuj ankoraŭ ne estas sur mi. ” La API liveras amason da dosieroj ordigitaj laŭ taksado, la demono elŝutas ilin, prenas ilin al la regionoj kaj liveras la dosierojn de tie. Jen la fundamenta diferenco inter pu/pp kaj Sun de kaŝmemoroj: ili donas la dosieron per si mem tuj, eĉ se ĉi tiu dosiero ne estas en la kaŝmemoro, kaj la kaŝmemoro unue elŝutas la dosieron al si, kaj poste komencas redoni ĝin.

En ĉi tiu kazo ni ricevas enhavo pli proksima al uzantoj kaj disvastigi la retan ŝarĝon. Ekzemple, nur el la Moskva kaŝmemoro ni disdonas pli ol 1 Tbit/s dum pintaj horoj.

Sed estas problemoj - kaŝmemorserviloj ne estas kaŭĉuko. Por super populara enhavo, foje ne estas sufiĉe da reto por aparta servilo. Niaj kaŝmemorserviloj estas 40-50 Gbit/s, sed ekzistas enhavo, kiu tute ŝtopas tian kanalon. Ni iras al efektivigo de stokado de pli ol unu kopio de popularaj dosieroj en la regiono. Mi esperas, ke ni efektivigos ĝin antaŭ la fino de la jaro.

Ni rigardis la ĝeneralan arkitekturon.

  • Antaŭaj serviloj, kiuj akceptas petojn.
  • Backends kiuj procesas petojn.
  • Stokejoj kiuj estas fermitaj per du specoj de prokuriloj.
  • Regionaj kaŝmemoroj.

Kio mankas al ĉi tiu diagramo? Kompreneble, la datumbazoj en kiuj ni stokas datumojn.

Datumbazoj aŭ motoroj

Ni nomas ilin ne datumbazoj, sed motoroj - Motoroj, ĉar ni praktike ne havas datumbazojn en la ĝenerale akceptita signifo.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Ĉi tio estas necesa mezuro. Ĉi tio okazis ĉar en 2008-2009, kiam VK havis eksplodan kreskon de populareco, la projekto funkciis tute sur MySQL kaj Memcache kaj estis problemoj. MySQL amis kraŝi kaj korupti dosierojn, post kio ĝi ne resaniĝos, kaj Memcache iom post iom degradis en rendimento kaj devis esti rekomencita.

Montriĝas, ke la ĉiam pli populara projekto havis konstantan stokadon, kiu koruptas datumojn, kaj kaŝmemoron, kiu malrapidiĝas. En tiaj kondiĉoj, estas malfacile disvolvi kreskantan projekton. Estis decidite provi reverki la kritikajn aferojn, kiujn la projekto koncentriĝis pri niaj propraj bicikloj.

La solvo estis sukcesa. Ekzistis ŝanco fari tion, same kiel ekstrema neceso, ĉar aliaj manieroj grimpi ne ekzistis tiutempe. Ne estis amaso da datumbazoj, NoSQL ankoraŭ ne ekzistis, estis nur MySQL, Memcache, PostrgreSQL - kaj jen ĝi.

Universala funkciado. La evoluo estis gvidita de nia teamo de C-programistoj kaj ĉio estis farita en konsekvenca maniero. Sendepende de la motoro, ili ĉiuj havis proksimume la saman dosierformaton skribitan al disko, la samajn lanĉajn parametrojn, prilaboris signalojn en la sama maniero, kaj kondutis proksimume same en kazo de randaj situacioj kaj problemoj. Kun la kresko de motoroj, estas oportune por administrantoj funkciigi la sistemon - ne ekzistas zoo, kiu devas esti prizorgata, kaj ili devas relerni kiel funkcii ĉiun novan datumbazon de triaj, kio ebligis rapide kaj oportune pliigi. ilia nombro.

Tipoj de motoroj

La teamo skribis sufiĉe da motoroj. Jen nur kelkaj el ili: amiko, sugestoj, bildo, ipdb, leteroj, listoj, protokoloj, memcached, meowdb, novaĵoj, nostradamus, foto, ludlistoj, pmemcached, sandbox, serĉo, stokado, ŝatoj, taskoj, ...

Por ĉiu tasko, kiu postulas specifan datumstrukturon aŭ prilaboras maltipaj petojn, la C-teamo skribas novan motoron. Kial ne.

Ni havas apartan motoron memcached, kiu similas al regula, sed kun amaso da bonaĵoj, kaj kiu ne malrapidiĝas. Ne ClickHouse, sed ĝi ankaŭ funkcias. Havebla aparte pmemcached Estas persista memcached, kiu ankaŭ povas stoki datumojn sur disko, krome, ol konvenas en RAM, por ne perdi datumojn dum rekomenco. Estas diversaj motoroj por individuaj taskoj: vostoj, listoj, aroj - ĉio, kion nia projekto postulas.

Aretoj

De kodperspektivo, ne necesas pensi pri motoroj aŭ datumbazoj kiel procezoj, estaĵoj aŭ okazoj. La kodo funkcias specife kun aretoj, kun grupoj de motoroj - unu tipo per areto. Ni diru, ke ekzistas memcached-grupo - ĝi estas nur grupo de maŝinoj.

La kodo tute ne bezonas scii la fizikan lokon, grandecon aŭ nombron da serviloj. Li iras al la areto uzante certan identigilon.

Por ke ĉi tio funkciu, vi devas aldoni unu plian enton, kiu situas inter la kodo kaj la motoroj - prokurilo.

RPC-prokurilo

Prokurilo konekta buso, sur kiu funkcias preskaŭ la tuta retejo. Samtempe ni havas neniu servo malkovro — anstataŭe, ekzistas agordo por ĉi tiu prokurilo, kiu konas la lokon de ĉiuj aretoj kaj ĉiuj fragmentoj de ĉi tiu areto. Jen kion faras administrantoj.

Programistoj tute ne zorgas pri kiom, kie kaj kiom ĝi kostas - ili simple iras al la areto. Ĉi tio permesas al ni multon. Ricevinte peton, la prokurilo alidirektas la peton, sciante kie - ĝi mem determinas tion.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

En ĉi tiu kazo, prokurilo estas punkto de protekto kontraŭ fiasko de servo. Se iu motoro malrapidiĝas aŭ kraŝas, tiam la prokurilo komprenas ĉi tion kaj respondas laŭe al la klienta flanko. Ĉi tio ebligas al vi forigi la tempon - la kodo ne atendas la respondon de la motoro, sed komprenas, ke ĝi ne funkcias kaj devas konduti iel malsame. La kodo devas esti preta por la fakto, ke la datumbazoj ne ĉiam funkcias.

Specifaj efektivigoj

Kelkfoje ni ankoraŭ vere volas havi ian ne-norman solvon kiel motoron. Samtempe oni decidis ne uzi nian pretan rpc-proxy, kreitan specife por niaj motoroj, sed fari apartan prokurilon por la tasko.

Por MySQL, kiun ni ankoraŭ havas ĉi tie kaj tie, ni uzas db-proxy, kaj por ClickHouse - Katidodomo.

Ĝi funkcias ĝenerale tiel. Estas certa servilo, ĝi rulas kPHP, Go, Python - ĝenerale, ajna kodo kiu povas uzi nian RPC-protokolon. La kodo funkcias loke sur RPC-prokurilo - ĉiu servilo, kie la kodo troviĝas, funkcias sian propran lokan prokurilon. Laŭ peto, la prokurilo komprenas kien iri.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Se unu motoro volas iri al alia, eĉ se ĝi estas najbaro, ĝi pasas tra prokurilo, ĉar la najbaro povas esti en alia datumcentro. La motoro ne dependu de scii la lokon de io alia ol si mem - ĉi tio estas nia norma solvo. Sed kompreneble estas esceptoj :)

Ekzemplo de TL-skemo laŭ kiu ĉiuj motoroj funkcias.

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;

Ĉi tio estas binara protokolo, kies plej proksima analogo estas protobuf. La skemo antaŭpriskribas laŭvolajn kampojn, kompleksajn tipojn - etendaĵojn de enkonstruitaj skalaroj, kaj demandojn. Ĉio funkcias laŭ ĉi tiu protokolo.

RPC super TL super TCP/UDP... UDP?

Ni havas RPC-protokolon por ekzekuti motorajn petojn, kiuj funkcias super la TL-skemo. Ĉio ĉi funkcias per TCP/UDP-konekto. TCP estas komprenebla, sed kial ni ofte bezonas UDP?

UDP helpas eviti la problemon de grandega nombro da konektoj inter serviloj. Se ĉiu servilo havas RPC-prokurilon kaj, ĝenerale, ĝi povas iri al iu ajn motoro, tiam ekzistas dekoj da miloj da TCP-konektoj por servilo. Estas ŝarĝo, sed ĝi estas senutila. En la kazo de UDP ĉi tiu problemo ne ekzistas.

Neniu redunda TCP-manpremo. Ĉi tio estas tipa problemo: kiam nova motoro aŭ nova servilo estas lanĉitaj, multaj TCP-konektoj estas establitaj samtempe. Por malgrandaj malpezaj petoj, ekzemple, UDP utila ŝarĝo, ĉiu komunikado inter la kodo kaj la motoro estas du UDP-pakaĵoj: unu flugas en unu direkto, la dua en la alia. Unu rondveturo - kaj la kodo ricevis respondon de la motoro sen manpremo.

Jes, ĉio nur funkcias kun tre malgranda procento de paka perdo. La protokolo havas subtenon por retranssendadoj kaj tempoforpasoj, sed se ni perdos multon, ni ricevos preskaŭ TCP, kio ne estas profita. Ni ne veturas UDP trans oceanojn.

Ni havas milojn da tiaj serviloj, kaj la skemo estas la sama: pako da motoroj estas instalita sur ĉiu fizika servilo. Ili estas plejparte unu-fadenaj por funkcii kiel eble plej rapide sen blokado, kaj estas shared kiel unu-fadenaj solvoj. Samtempe, ni havas nenion pli fidinda ol ĉi tiuj motoroj, kaj multe da atento estas pagita al konstanta datumstokado.

Konstanta datumstokado

Motoroj skribas binlogs. Binlog estas dosiero ĉe la fino de kiu evento por ŝanĝo de stato aŭ datumoj estas aldonita. En malsamaj solvoj ĝi nomiĝas malsame: binara log, Piediro, AOF, sed la principo estas la sama.

Por malhelpi la motoron relegi la tutan binlog dum multaj jaroj dum rekomencado, la motoroj skribas momentfotoj - aktuala stato. Se necese, ili unue legas el ĝi, kaj poste finas legi el la binlog. Ĉiuj binlogs estas skribitaj en la sama binara formato - laŭ la TL-skemo, tiel ke administrantoj povas administri ilin egale uzante siajn ilojn. Ne estas tia bezono de momentfotoj. Estas ĝenerala kaplinio, kiu indikas, kies momentfoto estas int, magio de la motoro, kaj kiu korpo ne gravas por iu ajn. Ĉi tio estas problemo kun la motoro kiu registris la momentfoton.

Mi rapide priskribos la principon de funkciado. Estas servilo, sur kiu funkcias la motoro. Li malfermas novan malplenan binlog por skribi kaj skribas eventon por ŝanĝo al ĝi.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Ĉe iu punkto, li aŭ decidas preni momentfoton mem, aŭ li ricevas signalon. La servilo kreas novan dosieron, skribas ĝian tutan staton en ĝi, aldonas la nunan binlog-grandecon - ofseton - al la fino de la dosiero, kaj daŭrigas skribon. Nova binlog ne estas kreita.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Iam, kiam la motoro rekomencis, estos kaj binlog kaj momentfoto sur la disko. La motoro legas la tutan momentfoton kaj levas sian staton je certa punkto.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Legas la pozicion kiu estis tiutempe kiam la momentfoto estis kreita kaj la grandecon de la binlog.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Legas la finon de la binlog por akiri la nunan staton kaj daŭre skribas pliajn eventojn. Ĉi tio estas simpla skemo; ĉiuj niaj motoroj funkcias laŭ ĝi.

Reproduktado de datumoj

Kiel rezulto, datumoj reproduktado en nia deklaro-bazita — ni skribas en la binlog ne ajnajn paĝŝanĝojn, sed nome ŝanĝpetoj. Tre simila al kio venas tra la reto, nur iomete modifita.

La sama skemo estas uzata ne nur por reproduktado, sed ankaŭ krei sekurkopiojn. Ni havas motoron - skribmastro kiu skribas al la binlog. En iu ajn alia loko, kie la administrantoj starigis ĝin, ĉi tiu binlog estas kopiita, kaj jen ĝi - ni havas sekurkopion.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Se vi bezonas leganta kopionPor redukti la CPU-legadŝarĝon, la legadmotoro estas simple lanĉita, kiu legas la finon de la binlog kaj efektivigas ĉi tiujn komandojn loke.

La malfruo ĉi tie estas tre malgranda, kaj eblas ekscii kiom la kopio postrestas malantaŭ la majstro.

Data sharding en RPC-prokurilo

Kiel funkcias sharding? Kiel la prokurilo komprenas al kiu areto shard sendi? La kodo ne diras: "Sendu por 15 fragmentoj!" - ne, tio estas farita de la prokurilo.

La plej simpla skemo estas firstint — la unua numero en la peto.

get(photo100_500) => 100 % N.

Ĉi tio estas ekzemplo por simpla memcached teksta protokolo, sed, kompreneble, demandoj povas esti kompleksaj kaj strukturitaj. La ekzemplo prenas la unuan nombron en la demando kaj la reston kiam dividite per la aretgrandeco.

Ĉi tio estas utila kiam ni volas havi datuman lokon de ununura ento. Ni diru, ke 100 estas uzanto aŭ grupidentigilo, kaj ni volas, ke ĉiuj datumoj de unu ento estu sur unu peceto por kompleksaj demandoj.

Se ni ne zorgas pri kiel petoj disvastiĝas tra la areto, ekzistas alia opcio - hakante la tutan peceton.

hash(photo100_500) => 3539886280 % N

Ni ankaŭ ricevas la haŝiŝon, la reston de la divido kaj la breĉetnumeron.

Ambaŭ ĉi tiuj opcioj nur funkcias se ni estas pretaj por tio, ke kiam ni pliigos la grandecon de la areto, ni dividos ĝin aŭ pliigos ĝin per multoblo da fojoj. Ekzemple, ni havis 16 pecetojn, ni ne havas sufiĉe, ni volas pli - ni povas sekure akiri 32 sen malfunkcio. Se ni volas pliigi ne multoblojn, estos malfunkcio, ĉar ni ne povos precize dividi ĉion sen perdoj. Ĉi tiuj opcioj estas utilaj, sed ne ĉiam.

Se ni bezonas aldoni aŭ forigi arbitran nombron da serviloj, ni uzas Konsekvenca hakado sur la ringo al la Ketama. Sed samtempe, ni tute perdas la lokon de la datumoj; ni devas kunfandi la peton al la areto por ke ĉiu peco resendas sian propran malgrandan respondon, kaj poste kunfandi la respondojn al la prokurilo.

Estas superspecifaj petoj. Ĝi aspektas jene: RPC-prokurilo ricevas la peton, determinas al kiu areto iri kaj determinas la breĉeton. Tiam ekzistas aŭ skribmajstroj, aŭ, se la areto havas kopisubtenon, ĝi sendas al kopio laŭ postulo. La prokurilo faras ĉion ĉi.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Ŝtipoj

Ni skribas protokolojn en pluraj manieroj. La plej evidenta kaj simpla estas skribi protokolojn al memcache.

ring-buffer: prefix.idx = line

Estas ŝlosila prefikso - la nomo de la protokolo, linio, kaj estas la grandeco de ĉi tiu protokolo - la nombro da linioj. Ni prenas hazardan nombron de 0 ĝis la nombro da linioj minus 1. La ŝlosilo en memcache estas prefikso kunligita kun ĉi tiu hazarda nombro. Ni konservas la protokollinion kaj la nunan tempon al la valoro.

Kiam necesas legi protokolojn, ni efektivigas Multi Get ĉiuj ŝlosiloj, ordigitaj laŭ tempo, kaj tiel ricevi produktadprotokolo en reala tempo. La skemo estas uzata kiam vi bezonas sencimigi ion en produktado en reala tempo, sen rompi ion ajn, sen halti aŭ permesi trafikon al aliaj maŝinoj, sed ĉi tiu protokolo ne daŭras longe.

Por fidinda konservado de ŝtipoj ni havas motoron logs-motoro. Ĝuste tial ĝi estis kreita kaj estas vaste uzata en grandega nombro da aretoj. La plej granda areto pri kiu mi konas stokas 600 TB da plenplenaj ŝtipoj.

La motoro estas tre malnova, estas aretoj kiuj jam aĝas 6-7 jarojn. Estas problemoj kun ĝi, kiujn ni provas solvi, ekzemple, ni komencis aktive uzi ClickHouse por stoki ŝtipojn.

Kolektante ŝtipojn en ClickHouse

Ĉi tiu diagramo montras kiel ni eniras niajn motorojn.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Estas kodo, kiu iras loke per RPC al la RPC-proxy, kaj ĝi komprenas kien iri al la motoro. Se ni volas skribi protokolojn en ClickHouse, ni devas ŝanĝi du partojn en ĉi tiu skemo:

  • anstataŭigi iun motoron per ClickHouse;
  • anstataŭigi la RPC-prokurilon, kiu ne povas aliri ClickHouse, per iu solvo, kiu povas, kaj per RPC.

La motoro estas simpla - ni anstataŭigas ĝin per servilo aŭ aro da serviloj per ClickHouse.

Kaj por iri al ClickHouse, ni faris Katidodomo. Se ni iros rekte de KittenHouse al ClickHouse, ĝi ne traktos. Eĉ sen petoj, ĝi sumiĝas de HTTP-konektoj de grandega nombro da maŝinoj. Por ke la skemo funkciu, sur servilo kun ClickHouse loka inversa prokurilo estas levita, kiu estas skribita en tia maniero ke ĝi povas elteni la postulatajn volumojn de ligoj. Ĝi ankaŭ povas bufri datumojn ene de si relative fidinde.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Kelkfoje ni ne volas efektivigi la RPC-skemon en ne-normaj solvoj, ekzemple en nginx. Tial KittenHouse havas la kapablon ricevi protokolojn per UDP.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Se la sendinto kaj ricevanto de la protokoloj funkcias sur la sama maŝino, tiam la probablo perdi UDP-pakon ene de la loka gastiganto estas sufiĉe malalta. Kiel kompromiso inter la bezono efektivigi RPC en triaparta solvo kaj fidindeco, ni simple uzas UDP-sendadon. Ni revenos al ĉi tiu skemo poste.

Monitorado

Ni havas du tipojn de protokoloj: tiuj kolektitaj de administrantoj sur siaj serviloj kaj tiuj skribitaj de programistoj el kodo. Ili respondas al du specoj de metrikoj: sistemo kaj produkto.

Sistema metriko

Ĝi funkcias ĉe ĉiuj niaj serviloj Netdatenoj, kiu kolektas statistikojn kaj sendas ilin al Grafito Karbono. Tial, ClickHouse estas uzata kiel konserva sistemo, kaj ne Whisper, ekzemple. Se necese, vi povas rekte legi de ClickHouse, aŭ uzi grafana por metrikoj, grafikaĵoj kaj raportoj. Kiel programistoj, ni havas sufiĉe da aliro al Netdata kaj Grafana.

Produktaj metrikoj

Por komforto, ni skribis multajn aferojn. Ekzemple, ekzistas aro de ordinaraj funkcioj, kiuj permesas vin skribi Kalkulojn, UniqueCounts-valorojn en statistikojn, kiuj estas senditaj ien pli.

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

$stats = statlogsStatData($params)

Poste, ni povas uzi ordigajn kaj grupigajn filtrilojn kaj fari ĉion, kion ni volas de statistiko - konstrui grafikaĵojn, agordi Gardhundojn.

Ni skribas tre multaj metrikoj la nombro de eventoj estas de 600 miliardoj ĝis 1 duiliono tage. Tamen ni volas konservi ilin almenaŭ kelkajn jarojnkompreni tendencojn en metriko. Kunmeti ĉion estas granda problemo, kiun ni ankoraŭ ne solvis. Mi rakontos al vi kiel ĝi funkcias dum la lastaj jaroj.

Ni havas funkciojn, kiuj skribas ĉi tiujn metrikojn al loka memcacheredukti la nombron da eniroj. Unufoje en mallonga tempoloke lanĉita statistiko-demono kolektas ĉiujn rekordojn. Poste, la demono kunfandas la metrikojn en du tavolojn de serviloj ŝtipoj-kolektantoj, kiu kunigas statistikojn de amaso da niaj maŝinoj por ke la tavolo malantaŭ ili ne mortu.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Se necese, ni povas skribi rekte al protokoloj-kolektantoj.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Sed skribi de kodo rekte al kolektantoj, preterirante stas-daemom, estas malbone skalebla solvo ĉar ĝi pliigas la ŝarĝon sur la kolektanto. La solvo taŭgas nur se ial ni ne povas levi la memcache stats-daemon sur la maŝino, aŭ ĝi kraŝis kaj ni iris rekte.

Poste, ŝtipoj-kolektantoj kunfandas statistikojn en meowDB - ĉi tio estas nia datumbazo, kiu ankaŭ povas konservi metrikojn.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Tiam ni povas fari binarajn "preskaŭ-SQL" elektojn de la kodo.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Eksperimento

En la somero de 2018, ni havis internan hakatonon, kaj venis la ideo provi anstataŭigi la ruĝan parton de la diagramo per io, kio povus stoki metrikojn en ClickHouse. Ni havas protokolojn en ClickHouse - kial ne provi ĝin?

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Ni havis skemon, kiu skribis protokolojn per KittenHouse.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

Ni decidis aldonu alian "*Domon" al la diagramo, kiu ricevos ĝuste la metrikojn en la formato kiel nia kodo skribas ilin per UDP. Tiam ĉi tiu *Domo igas ilin enigaĵoj, kiel ŝtipoj, kiujn KittenHouse komprenas. Li povas perfekte liveri ĉi tiujn protokolojn al ClickHouse, kiu devus povi legi ilin.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

La skemo kun memcache, stats-daemon kaj logs-collectors datumbazo estas anstataŭigita per ĉi tiu.

Oftaj Demandoj pri arkitekturo kaj laboro de VKontakte

La skemo kun memcache, stats-daemon kaj logs-collectors datumbazo estas anstataŭigita per ĉi tiu.

  • Estas sendo de kodo ĉi tie, kiu estas skribita loke en StatsHouse.
  • StatsHouse skribas UDP-metrikojn, jam konvertitajn en SQL-enigaĵojn, al KittenHouse en aroj.
  • KittenHouse sendas ilin al ClickHouse.
  • Se ni volas legi ilin, tiam ni legas ilin preterirante StatsHouse - rekte de ClickHouse uzante regulan SQL.

Ĉu ankoraŭ eksperimento, sed ni ŝatas kiel ĝi rezultas. Se ni riparos la problemojn kun la skemo, tiam eble ni tute ŝanĝos al ĝi. Persone, mi esperas ke jes.

Skemo ne ŝparas feron. Necesas malpli da serviloj, lokaj statistiko-demonoj kaj protokoloj-kolektantoj ne estas bezonataj, sed ClickHouse postulas pli grandan servilon ol tiuj en la nuna skemo. Necesas malpli da serviloj, sed ili devas esti pli multekostaj kaj pli potencaj.

Deploji

Unue, ni rigardu la PHP-deplojon. Ni evoluas en iri: uzi GitLab и TeamCity por deplojo. Evoluaj branĉoj estas kunfanditaj en la majstran branĉon, de la majstro por testado ili estas kunfanditaj en enscenigon, kaj de enscenigo en produktadon.

Antaŭ deplojo, la nuna produktadbranĉo kaj la antaŭa estas prenitaj, kaj diferencaj dosieroj estas konsiderataj en ili - ŝanĝoj: kreitaj, forigitaj, ŝanĝitaj. Ĉi tiu ŝanĝo estas registrita en la binlog de speciala copyfast-motoro, kiu povas rapide reprodukti ŝanĝojn al nia tuta servila floto. Kio estas uzata ĉi tie ne estas kopiado rekte, sed klaĉreproduktado, kiam unu servilo sendas ŝanĝojn al siaj plej proksimaj najbaroj, tiujn al iliaj najbaroj, ktp. Ĉi tio ebligas al vi ĝisdatigi la kodon en dekoj kaj unuoj da sekundoj tra la tuta floto. Kiam la ŝanĝo atingas la lokan kopion, ĝi aplikas ĉi tiujn diakilojn al ĝia loka dosiersistemo. Rollback ankaŭ estas efektivigita laŭ la sama skemo.

Ni ankaŭ deplojas kPHP multe kaj ĝi ankaŭ havas sian propran disvolviĝon iri laŭ la supra diagramo. Ekde ĉi tio HTTP-servilo binara, tiam ni ne povas produkti dif - la eldonbinaro pezas centojn da MB. Tial, ekzistas alia opcio ĉi tie - la versio estas skribita al binlog copyfast. Kun ĉiu konstruo ĝi pliiĝas, kaj dum retroiro ĝi ankaŭ pliiĝas. Versio reproduktita al serviloj. Lokaj kopiistoj vidas, ke nova versio eniris la binlog, kaj per la sama klaĉreproduktado ili prenas la lastan version de la duumaro por si, sen lacigi nian majstran servilon, sed zorge disvastigante la ŝarĝon tra la reto. Kio sekvas gracia relanĉo por la nova versio.

Por niaj motoroj, kiuj ankaŭ estas esence binaraj, la skemo estas tre simila:

  • git majstra branĉo;
  • duuma en .deb;
  • la versio estas skribita al binlog copyfast;
  • reproduktita al serviloj;
  • la servilo eltiras freŝan .dep;
  • dpkg -i;
  • gracia relanĉo al nova versio.

La diferenco estas, ke nia binaro estas pakita en arkivoj .deb, kaj dum elpumpado ili dpkg -i estas metitaj sur la sistemon. Kial kPHP estas deplojita kiel duuma, kaj motoroj estas deplojitaj kiel dpkg? Okazis tiel. Ĝi funkcias - ne tuŝu ĝin.

Utilaj ligoj:

Aleksej Akuloviĉ estas unu el tiuj, kiuj, kadre de la Programa Komitato, helpas PHP Rusio la 17-an de majo fariĝos la plej granda evento por PHP-programistoj en la lastaj tempoj. Rigardu kian bonegan komputilon ni havas, kion parolantoj (du el ili disvolvas PHP-kernon!) - ŝajnas io, kion vi ne povas maltrafi, se vi skribas PHP.

fonto: www.habr.com

Aldoni komenton