Vyf studente en drie verspreide sleutelwaarde-winkels

Of hoe ons 'n kliënt C++-biblioteek vir ZooKeeper, ens en Consul KV geskryf het

In die wêreld van verspreide stelsels is daar 'n aantal tipiese take: die stoor van inligting oor die samestelling van die groep, die bestuur van die konfigurasie van nodusse, die opsporing van foutiewe nodusse, die keuse van 'n leier en ander. Om hierdie probleme op te los, is spesiale verspreide stelsels geskep - koördineringsdienste. Nou sal ons in drie van hulle belangstel: ZooKeeper, etcd en Consul. Uit al die ryk funksionaliteit van Consul, sal ons fokus op Consul KV.

Vyf studente en drie verspreide sleutelwaarde-winkels

In wese is al hierdie stelsels fouttolerante, lineariseerbare sleutelwaarde-winkels. Alhoewel hul datamodelle beduidende verskille het, wat ons later sal bespreek, los hulle dieselfde praktiese probleme op. Uiteraard is elke toepassing wat die koördinasiediens gebruik, aan een daarvan gekoppel, wat kan lei tot die behoefte om verskeie stelsels in een datasentrum te ondersteun wat dieselfde probleme vir verskillende toepassings oplos.

Die idee om hierdie probleem op te los, het by 'n Australiese konsultasie-agentskap ontstaan, en dit het op ons, 'n klein span studente, geval om dit te implementeer, en dit is waaroor ek gaan praat.

Ons het daarin geslaag om 'n biblioteek te skep wat 'n gemeenskaplike koppelvlak bied om met ZooKeeper, etcd en Consul KV te werk. Die biblioteek is in C++ geskryf, maar daar is planne om dit na ander tale oor te dra.

Data modelle

Om 'n gemeenskaplike koppelvlak vir drie verskillende stelsels te ontwikkel, moet jy verstaan ​​wat hulle gemeen het en hoe hulle verskil. Kom ons vind dit uit.

Dieretuinopsigter

Vyf studente en drie verspreide sleutelwaarde-winkels

Die sleutels is in 'n boom georganiseer en word nodusse genoem. Gevolglik kan jy vir 'n nodus 'n lys van sy kinders kry. Die bewerkings van die skep van 'n znode (skep) en die verandering van 'n waarde (setData) word geskei: slegs bestaande sleutels kan gelees en verander word. Horlosies kan gekoppel word aan die bewerkings om die bestaan ​​van 'n nodus na te gaan, 'n waarde te lees en kinders te kry. Watch is 'n eenmalige sneller wat begin wanneer die weergawe van die ooreenstemmende data op die bediener verander. Efemere nodusse word gebruik om mislukkings op te spoor. Hulle is gekoppel aan die sessie van die kliënt wat hulle geskep het. Wanneer 'n kliënt 'n sessie sluit of ophou om ZooKeeper van sy bestaan ​​in kennis te stel, word hierdie nodusse outomaties uitgevee. Eenvoudige transaksies word ondersteun - 'n stel bewerkings wat óf almal slaag óf misluk as dit nie vir ten minste een van hulle moontlik is nie.

ens

Vyf studente en drie verspreide sleutelwaarde-winkels

Die ontwikkelaars van hierdie stelsel is duidelik deur ZooKeeper geïnspireer, en het dus alles anders gedoen. Daar is geen hiërargie van sleutels nie, maar hulle vorm 'n leksikografies geordende stel. Jy kan alle sleutels wat aan 'n sekere reeks behoort, kry of uitvee. Hierdie struktuur mag dalk vreemd voorkom, maar dit is eintlik baie ekspressief, en 'n hiërargiese siening kan maklik daardeur nagevolg word.

etcd het nie 'n standaard vergelyk-en-stel-operasie nie, maar dit het wel iets beter: transaksies. Natuurlik bestaan ​​hulle in al drie stelsels, maar ens-transaksies is veral goed. Hulle bestaan ​​uit drie blokke: tjek, sukses, mislukking. Die eerste blok bevat 'n stel voorwaardes, die tweede en derde - bewerkings. Die transaksie word atomies uitgevoer. As alle voorwaardes waar is, word die suksesblok uitgevoer, anders word die mislukkingsblok uitgevoer. In API 3.3 kan sukses- en mislukkingsblokke geneste transaksies bevat. Dit wil sê, dit is moontlik om atomies voorwaardelike konstrukte van byna arbitrêre nesvlak uit te voer. Jy kan meer leer oor waaruit tjeks en bedrywighede bestaan dokumentasie.

Horlosies bestaan ​​ook hier, hoewel hulle 'n bietjie meer ingewikkeld is en herbruikbaar is. Dit wil sê, nadat jy 'n horlosie op 'n sleutelreeks geïnstalleer het, sal jy alle opdaterings in hierdie reeks ontvang totdat jy die horlosie kanselleer, en nie net die eerste een nie. In etcd is die analoog van ZooKeeper-kliëntsessies huurkontrakte.

Konsul K.V.

Hier is ook geen streng hiërargiese struktuur nie, maar Consul kan die voorkoms skep dat dit bestaan: jy kan alle sleutels met die gespesifiseerde voorvoegsel kry en uitvee, dit wil sê, werk met die "subboom" van die sleutel. Sulke navrae word rekursief genoem. Daarbenewens kan Consul slegs sleutels kies wat nie die gespesifiseerde karakter na die voorvoegsel bevat nie, wat ooreenstem met die verkryging van onmiddellike "kinders". Maar dit is die moeite werd om te onthou dat dit presies die voorkoms van 'n hiërargiese struktuur is: dit is heel moontlik om 'n sleutel te skep as sy ouer nie bestaan ​​nie of 'n sleutel wat kinders het uitvee, terwyl die kinders steeds in die stelsel gestoor sal word.

Vyf studente en drie verspreide sleutelwaarde-winkels
In plaas van horlosies, het Consul blokkeer HTTP-versoeke. In wese is dit gewone oproepe na die dataleesmetode, waarvoor, saam met ander parameters, die laaste bekende weergawe van die data aangedui word. As die huidige weergawe van die ooreenstemmende data op die bediener groter is as die gespesifiseerde een, word die antwoord onmiddellik teruggestuur, anders - wanneer die waarde verander. Daar is ook sessies wat enige tyd aan sleutels geheg kan word. Dit is opmerklik dat anders as etcd en ZooKeeper, waar die verwydering van sessies lei tot die verwydering van geassosieerde sleutels, daar 'n modus is waarin die sessie eenvoudig van hulle ontkoppel word. Beskikbaar transaksies, sonder takke, maar met allerhande tjeks.

Om dit alles saam te voeg

ZooKeeper het die strengste datamodel. Die ekspressiewe reeksnavrae wat in etcd beskikbaar is, kan nie effektief in ZooKeeper of Consul nagevolg word nie. Om die beste van al die dienste te probeer inkorporeer, het ons uiteindelik 'n koppelvlak byna gelykstaande aan die ZooKeeper-koppelvlak gehad met die volgende belangrike uitsonderings:

  • volgorde, houer en TTL nodusse nie ondersteun nie
  • ACL's word nie ondersteun nie
  • die stel metode skep 'n sleutel as dit nie bestaan ​​nie (in ZK gee setData 'n fout in hierdie geval terug)
  • stel- en kas-metodes word geskei (in ZK is dit in wese dieselfde ding)
  • die uitveemetode verwyder 'n nodus saam met sy subboom (in ZK gee delete 'n fout terug as die nodus kinders het)
  • Vir elke sleutel is daar net een weergawe - die waarde weergawe (in ZK daar is drie van hulle)

Die verwerping van opeenvolgende nodusse is te wyte aan die feit dat etcd en Consul nie ingeboude ondersteuning daarvoor het nie, en dit kan maklik deur die gebruiker geïmplementeer word bo-op die gevolglike biblioteekkoppelvlak.

Om gedrag soortgelyk aan ZooKeeper te implementeer wanneer 'n hoekpunt verwyder word, sal vereis dat 'n aparte kindteller vir elke sleutel in etcd en Consul in stand gehou word. Aangesien ons probeer vermy het om meta-inligting te stoor, is daar besluit om die hele subboom uit te vee.

Subtiliteite van implementering

Kom ons kyk van naderby na sommige aspekte van die implementering van die biblioteekkoppelvlak in verskillende stelsels.

Hiërargie in ens

Die handhawing van 'n hiërargiese siening in etcd was een van die interessantste take. Reeksnavrae maak dit maklik om 'n lys sleutels met 'n gespesifiseerde voorvoegsel op te spoor. Byvoorbeeld, as jy alles nodig het wat begin met "/foo", jy vra vir 'n reeks ["/foo", "/fop"). Maar dit sal die hele subboom van die sleutel terugstuur, wat dalk nie aanvaarbaar is as die subboom groot is nie. Ons het aanvanklik beplan om 'n sleutelvertalingsmeganisme te gebruik, geïmplementeer in zetcd. Dit behels die byvoeging van een greep aan die begin van die sleutel, gelyk aan die diepte van die nodus in die boom. Kom ek gee vir jou 'n voorbeeld.

"/foo" -> "u01/foo"
"/foo/bar" -> "u02/foo/bar"

Kry dan alle onmiddellike kinders van die sleutel "/foo" moontlik deur 'n reeks aan te vra ["u02/foo/", "u02/foo0"). Ja, in ASCII "0" staan ​​reg daarna "/".

Maar hoe om die verwydering van 'n hoekpunt in hierdie geval te implementeer? Dit blyk dat jy alle reekse van die tipe moet uitvee ["uXX/foo/", "uXX/foo0") vir XX van 01 tot FF. En toe loop ons raak operasie getal limiet binne een transaksie.

As gevolg hiervan is 'n eenvoudige sleutelomskakelingstelsel uitgevind, wat dit moontlik gemaak het om beide die verwydering van 'n sleutel en die verkryging van 'n lys kinders effektief te implementeer. Dit is genoeg om 'n spesiale karakter voor die laaste teken by te voeg. Byvoorbeeld:

"/very" -> "/u00very"
"/very/long" -> "/very/u00long"
"/very/long/path" -> "/very/long/u00path"

Vee dan die sleutel uit "/very" verander in skrapping "/u00very" en reeks ["/very/", "/very0"), en kry alle kinders - in 'n versoek vir sleutels uit die reeks ["/very/u00", "/very/u01").

Verwyder 'n sleutel in ZooKeeper

Soos ek reeds genoem het, in ZooKeeper kan jy nie 'n nodus uitvee as dit kinders het nie. Ons wil die sleutel saam met die subboom uitvee. Wat moet ek doen? Ons doen dit met optimisme. Eerstens deurkruis ons die subboom rekursief en kry die kinders van elke hoekpunt met 'n aparte navraag. Dan bou ons 'n transaksie wat probeer om alle nodusse van die subboom in die regte volgorde uit te vee. Natuurlik kan veranderinge plaasvind tussen die lees van 'n subboom en die verwydering daarvan. In hierdie geval sal die transaksie misluk. Boonop kan die subboom tydens die leesproses verander. 'n Versoek vir die kinders van die volgende nodus kan 'n fout terugstuur as hierdie nodus byvoorbeeld reeds uitgevee is. In beide gevalle herhaal ons die hele proses weer.

Hierdie benadering maak die uitvee van 'n sleutel baie ondoeltreffend as dit kinders het, en selfs meer so as die toepassing voortgaan om met die subboom te werk, om sleutels uit te vee en te skep. Dit het ons egter toegelaat om die implementering van ander metodes in etcd en Consul te bemoeilik.

speel af in ZooKeeper

In ZooKeeper is daar aparte metodes wat met die boomstruktuur werk (create, delete, getChildren) en wat werk met data in nodusse (setData, getData) Boonop het alle metodes streng voorwaardes: skep sal 'n fout terugstuur as die nodus reeds het geskep, verwyder of stel Data – as dit nie reeds bestaan ​​nie. Ons het 'n vasgestelde metode nodig gehad wat geroep kan word sonder om aan die teenwoordigheid van 'n sleutel te dink.

Een opsie is om 'n optimistiese benadering te volg, soos met skrapping. Kyk of 'n nodus bestaan. As dit bestaan, bel setData, anders skep. As die laaste metode 'n fout opgelewer het, herhaal dit weer. Die eerste ding om op te let is dat die bestaanstoets doelloos is. Jy kan dadelik skep. Suksesvolle voltooiing sal beteken dat die nodus nie bestaan ​​het nie en dit geskep is. Andersins, skep sal die toepaslike fout terugstuur, waarna jy setData moet bel. Natuurlik, tussen oproepe, kan 'n hoekpunt deur 'n mededingende oproep uitgevee word, en setData sal ook 'n fout terugstuur. In hierdie geval kan jy dit alles weer doen, maar is dit die moeite werd?

As beide metodes 'n fout gee, weet ons verseker dat 'n mededingende skrapping plaasgevind het. Kom ons stel ons voor dat hierdie uitvee plaasgevind het na die oproepstel. Dan is watter betekenis ons ook al probeer vasstel, reeds uitgevee. Dit beteken dat ons kan aanvaar dat die stel suksesvol uitgevoer is, selfs al is daar eintlik niks geskryf nie.

Meer tegniese besonderhede

In hierdie afdeling sal ons 'n breek neem van verspreide stelsels en oor kodering praat.
Een van die hoofvereistes van die kliënt was kruisplatform: ten minste een van die dienste moet op Linux, MacOS en Windows ondersteun word. Aanvanklik het ons net vir Linux ontwikkel, en het later op ander stelsels begin toets. Dit het baie probleme veroorsaak, wat vir 'n geruime tyd heeltemal onduidelik was hoe om te benader. Gevolglik word al drie koördineringsdienste nou op Linux en MacOS ondersteun, terwyl slegs Consul KV op Windows ondersteun word.

Van die begin af het ons probeer om klaargemaakte biblioteke te gebruik om toegang tot dienste te verkry. In die geval van ZooKeeper het die keuse geval ZooKeeper C++, wat uiteindelik nie kon saamstel op Windows nie. Dit is egter nie verbasend nie: die biblioteek is slegs as Linux geposisioneer. Vir Konsul was die enigste opsie ppkonsul. Ondersteuning moes daarby gevoeg word sessies и transaksies. Vir etcd is 'n volwaardige biblioteek wat die nuutste weergawe van die protokol ondersteun nie gevind nie, so ons gegenereerde grpc-kliënt.

Geïnspireer deur die asinchrone koppelvlak van die ZooKeeper C++ biblioteek, het ons besluit om ook 'n asinchrone koppelvlak te implementeer. ZooKeeper C++ gebruik toekomstige/belofte-primitiewe hiervoor. In STL word hulle ongelukkig baie beskeie geïmplementeer. Byvoorbeeld, nee dan metode, wat die geslaagde funksie toepas op die resultaat van die toekoms wanneer dit beskikbaar word. In ons geval is so 'n metode nodig om die resultaat in die formaat van ons biblioteek om te skakel. Om hierdie probleem te omseil, moes ons ons eie eenvoudige draadpoel implementeer, aangesien ons op versoek van die kliënt nie swaar derdeparty-biblioteke soos Boost kon gebruik nie.

Ons destydse implementering werk so. Wanneer geroep word, word 'n bykomende belofte/toekomstige paar geskep. Die nuwe toekoms word teruggestuur, en die geslaagde een word saam met die ooreenstemmende funksie en 'n bykomende belofte in die tou geplaas. 'n Draad uit die poel kies verskeie termynkontrakte uit die tou en stem hulle met wait_for. Wanneer 'n resultaat beskikbaar word, word die ooreenstemmende funksie geroep en die terugkeerwaarde daarvan word na die belofte oorgedra.

Ons het dieselfde draadpoel gebruik om navrae na etcd en Consul uit te voer. Dit beteken dat die onderliggende biblioteke deur verskeie verskillende drade verkry kan word. ppconsul is nie draadveilig nie, so oproepe daarna word deur slotte beskerm.
Jy kan met grpc van verskeie drade werk, maar daar is subtiliteite. In etcd word horlosies geïmplementeer via grpc-strome. Dit is tweerigtingkanale vir boodskappe van 'n sekere soort. Die biblioteek skep 'n enkele draad vir alle horlosies en 'n enkele draad wat inkomende boodskappe verwerk. So grpc verbied parallelle skryf om te stroom. Dit beteken dat wanneer u 'n horlosie inisialiseer of uitvee, u moet wag totdat die vorige versoek gestuur is voordat u die volgende een stuur. Ons gebruik vir sinchronisasie voorwaardelike veranderlikes.

Totale

Kyk vir jouself: liboffkv.

Ons span: Raed Romanov, Ivan Glushenkov, Dmitri Kamaldinov, Victor Krapivensky, Vitali Ivanin.

Bron: will.com

Voeg 'n opmerking