Viisi opiskelijaa ja kolme hajautettua avainarvokauppaa

Tai kuinka kirjoitimme C++-asiakaskirjaston ZooKeeperille, etcd:lle ja Consul KV:lle

Hajautettujen järjestelmien maailmassa on useita tyypillisiä tehtäviä: tietojen tallentaminen klusterin kokoonpanosta, solmujen konfiguroinnin hallinta, viallisten solmujen havaitseminen, johtajan valinta muut. Näiden ongelmien ratkaisemiseksi on luotu erityisiä hajautettuja järjestelmiä - koordinointipalveluita. Nyt olemme kiinnostuneita niistä kolmesta: ZooKeeper, etcd ja Consul. Kaikista Consulin monipuolisista toiminnoista keskitymme Consul KV:hen.

Viisi opiskelijaa ja kolme hajautettua avainarvokauppaa

Pohjimmiltaan kaikki nämä järjestelmät ovat vikasietoisia, linearisoitavia avainarvovarastoja. Vaikka heidän tietomalleissaan on merkittäviä eroja, joista keskustelemme myöhemmin, ne ratkaisevat samat käytännön ongelmat. Ilmeisesti jokainen koordinointipalvelua käyttävä sovellus on sidottu yhteen niistä, mikä voi johtaa tarpeeseen tukea useita järjestelmiä yhdessä konesalissa, jotka ratkaisevat samat ongelmat eri sovelluksille.

Ajatus tämän ongelman ratkaisemisesta sai alkunsa australialaiselta konsulttitoimistolta, ja meidän, pienen opiskelijaryhmän tehtävänä oli toteuttaa se, mistä aion puhua.

Onnistuimme luomaan kirjaston, joka tarjoaa yhteisen käyttöliittymän ZooKeeperin, etcd:n ja Consul KV:n kanssa työskentelyyn. Kirjasto on kirjoitettu C++-kielellä, mutta se on tarkoitus siirtää muille kielille.

Tietomallit

Jotta voit kehittää yhteisen käyttöliittymän kolmelle eri järjestelmälle, sinun on ymmärrettävä, mitä niillä on yhteistä ja miten ne eroavat toisistaan. Selvitetään se.

ZooKeeper

Viisi opiskelijaa ja kolme hajautettua avainarvokauppaa

Avaimet on järjestetty puuhun ja niitä kutsutaan solmuiksi. Näin ollen solmulle saat luettelon sen lapsista. Zsolmun luonti (create) ja arvon muuttaminen (setData) on erotettu toisistaan: vain olemassa olevia avaimia voidaan lukea ja muuttaa. Kellot voidaan liittää solmun olemassaolon tarkistamiseen, arvon lukemiseen ja lasten hankkimiseen. Watch on kertaluonteinen laukaisu, joka käynnistyy, kun vastaavien tietojen versio palvelimella muuttuu. Efemeraalisia solmuja käytetään vikojen havaitsemiseen. Ne on sidottu ne luoneen asiakkaan istuntoon. Kun asiakas sulkee istunnon tai lakkaa ilmoittamasta ZooKeeperille sen olemassaolosta, nämä solmut poistetaan automaattisesti. Yksinkertaisia ​​tapahtumia tuetaan - joukko toimintoja, jotka joko onnistuvat tai epäonnistuvat, jos tämä ei ole mahdollista vähintään yhdelle niistä.

jne

Viisi opiskelijaa ja kolme hajautettua avainarvokauppaa

Tämän järjestelmän kehittäjät olivat selvästi ZooKeeperin inspiroimia, ja siksi he tekivät kaiken eri tavalla. Avaimilla ei ole hierarkiaa, vaan ne muodostavat leksikografisesti järjestetyn joukon. Voit hankkia tai poistaa kaikki tiettyyn alueeseen kuuluvat avaimet. Tämä rakenne voi tuntua oudolta, mutta itse asiassa se on hyvin ilmeikäs ja hierarkkista näkemystä voidaan helposti jäljitellä sen avulla.

etcd:ssä ei ole standardia vertaa ja aseta -toimintoa, mutta siinä on jotain parempaa: tapahtumat. Tietenkin niitä on kaikissa kolmessa järjestelmässä, mutta etcd-tapahtumat ovat erityisen hyviä. Ne koostuvat kolmesta lohkosta: check, menestys, epäonnistuminen. Ensimmäinen lohko sisältää joukon ehtoja, toinen ja kolmas - toiminnot. Kauppa toteutetaan atomaalisesti. Jos kaikki ehdot ovat tosia, onnistumislohko suoritetaan, muuten epäonnistumislohko suoritetaan. API 3.3:ssa onnistumis- ja epäonnistumislohkot voivat sisältää sisäkkäisiä tapahtumia. Toisin sanoen on mahdollista suorittaa atomisesti lähes mielivaltaisen sisäkkäistason ehdollisia rakenteita. Saat lisätietoja siitä, mistä tarkastukset ja toiminnot ovat olemassa dokumentointi.

Kelloja on myös täällä, vaikka ne ovatkin hieman monimutkaisempia ja uudelleenkäytettäviä. Eli kun olet asentanut kellon avainalueelle, saat kaikki tämän alueen päivitykset, kunnes peruutat kellon, et vain ensimmäistä. etcd:ssä ZooKeeper-asiakasistuntojen analogit ovat vuokrasopimukset.

Konsuli K.V.

Täällä ei myöskään ole tiukkaa hierarkkista rakennetta, mutta Consul voi luoda sellaisen vaikutelman, että se on olemassa: voit saada ja poistaa kaikki avaimet määritetyllä etuliitteellä, eli työskennellä avaimen "alipuun" kanssa. Tällaisia ​​kyselyjä kutsutaan rekursiivisiksi. Lisäksi konsuli voi valita vain avaimet, jotka eivät sisällä määritettyä merkkiä etuliitteen jälkeen, mikä vastaa välittömien "lasten" saamista. Mutta on syytä muistaa, että tämä on juuri hierarkkisen rakenteen ulkonäkö: on täysin mahdollista luoda avain, jos sen vanhempia ei ole, tai poistaa avain, jolla on lapsia, kun taas lapset säilyvät edelleen järjestelmään.

Viisi opiskelijaa ja kolme hajautettua avainarvokauppaa
Kellojen sijaan Consul estää HTTP-pyynnöt. Pohjimmiltaan nämä ovat tavallisia tiedonlukumenetelmän kutsuja, joille muiden parametrien ohella ilmoitetaan viimeisin tiedossa oleva versio. Jos vastaavan tiedon nykyinen versio palvelimella on suurempi kuin määritetty, vastaus palautetaan välittömästi, muuten - arvon muuttuessa. On myös istuntoja, jotka voidaan liittää avaimiin milloin tahansa. On syytä huomata, että toisin kuin etcd ja ZooKeeper, joissa istuntojen poistaminen johtaa siihen liittyvien avainten poistamiseen, on olemassa tila, jossa istunto yksinkertaisesti irrotetaan niistä. Saatavilla liiketoimia, ilman oksia, mutta kaikenlaisilla shekeillä.

Laittamalla kaikki yhteen

ZooKeeperillä on tiukin tietomalli. Etdd:ssä saatavilla olevia ilmeikkäitä aluekyselyitä ei voi emuloida tehokkaasti ZooKeeperissä tai Consulissa. Yrittäessämme sisällyttää kaikkien palveluiden parhaat puolet, päädyimme lähes ZooKeeper-käyttöliittymää vastaavaan käyttöliittymään seuraavin merkittävin poikkeuksin:

  • sekvenssi, kontti ja TTL-solmut ei tueta
  • ACL-luetteloita ei tueta
  • set-metodi luo avaimen, jos sitä ei ole olemassa (ZK:ssa setData palauttaa virheen tässä tapauksessa)
  • set- ja cas-menetelmät erotetaan toisistaan ​​(ZK:ssa ne ovat olennaisesti sama asia)
  • erase-menetelmä poistaa solmun ja sen alipuun (ZK:ssa delete palauttaa virheen, jos solmulla on lapsia)
  • Jokaiselle avaimelle on vain yksi versio - arvoversio (ZK niitä on kolme)

Peräkkäisten solmujen hylkääminen johtuu siitä, että etcd:llä ja Consulilla ei ole niille sisäänrakennettua tukea ja ne voidaan helposti toteuttaa käyttäjän toimesta syntyvän kirjastorajapinnan päälle.

ZooKeeperin kaltaisen käyttäytymisen toteuttaminen kärkipisteen poistamisen yhteydessä vaatisi erillisen lapsilaskurin ylläpitämistä jokaiselle avaimelle etcd:ssä ja Consulissa. Koska yritimme välttää metatietojen tallentamista, päätettiin poistaa koko alipuu.

Toteutuksen hienouksia

Tarkastellaanpa tarkemmin joitain puolia kirjastorajapinnan toteuttamisessa eri järjestelmissä.

Hierarkia jne.:ssä

Hierarkkisen näkemyksen ylläpitäminen etcd:ssä osoittautui yhdeksi mielenkiintoisimmista tehtävistä. Aluekyselyiden avulla on helppo hakea avainluettelo tietyllä etuliitteellä. Esimerkiksi, jos tarvitset kaikkea, mikä alkaa "/foo", pyydät vaihteluväliä ["/foo", "/fop"). Mutta tämä palauttaisi avaimen koko alipuun, mikä ei ehkä ole hyväksyttävää, jos alipuu on suuri. Aluksi suunnittelimme käyttävämme keskeistä käännösmekanismia, toteutettu zetcd:ssä. Se sisältää yhden tavun lisäämisen avaimen alkuun, joka on yhtä suuri kuin puun solmun syvyys. Annan sinulle esimerkin.

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

Hanki sitten kaikki avaimen välittömät lapset "/foo" mahdollista pyytämällä valikoimaa ["u02/foo/", "u02/foo0"). Kyllä, ASCII:ssa "0" seisoo heti perään "/".

Mutta kuinka toteuttaa huippupisteen poistaminen tässä tapauksessa? Osoittautuu, että sinun on poistettava kaikki tyypin alueet ["uXX/foo/", "uXX/foo0") XX:lle 01–FF. Ja sitten törmäsimme operaatioiden määräraja yhden kaupan sisällä.

Tuloksena keksittiin yksinkertainen avainten muunnosjärjestelmä, joka mahdollisti tehokkaasti sekä avaimen poistamisen että lapsiluettelon saamisen. Riittää, kun lisäät erikoismerkin ennen viimeistä merkkiä. Esimerkiksi:

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

Sen jälkeen avain poistetaan "/very" muuttuu poistoksi "/u00very" ja valikoima ["/very/", "/very0"), ja kaikkien lasten hankkiminen - valikoiman avaimia koskevassa pyynnössä ["/very/u00", "/very/u01").

Avaimen poistaminen ZooKeeperistä

Kuten jo mainitsin, ZooKeeperissä et voi poistaa solmua, jos sillä on lapsia. Haluamme poistaa avaimen alipuun kanssa. Mitä minun pitäisi tehdä? Teemme tämän optimistisesti. Ensin kuljemme rekursiivisesti alipuun läpi ja saamme kunkin kärjen lapset erillisellä kyselyllä. Sitten rakennamme tapahtuman, joka yrittää poistaa kaikki alipuun solmut oikeassa järjestyksessä. Tietenkin muutoksia voi tapahtua alipuun lukemisen ja sen poistamisen välillä. Tässä tapauksessa kauppa epäonnistuu. Lisäksi alipuu voi muuttua lukuprosessin aikana. Seuraavan solmun lapsia koskeva pyyntö voi palauttaa virheilmoituksen, jos esimerkiksi tämä solmu on jo poistettu. Molemmissa tapauksissa toistamme koko prosessin uudelleen.

Tämä lähestymistapa tekee avaimen poistamisesta erittäin tehotonta, jos sillä on lapsia, ja vielä enemmän, jos sovellus jatkaa työskentelyä alipuun kanssa, poistamalla ja luomalla avaimia. Tämä kuitenkin antoi meille mahdollisuuden välttää muiden menetelmien toteuttamista monimutkaisemmasta etcd:ssä ja Consulissa.

asetettu ZooKeeperissä

ZooKeeperissä on erilliset menetelmät, jotka toimivat puurakenteen kanssa (create, delete, getChildren) ja jotka toimivat solmujen tietojen kanssa (setData, getData) Lisäksi kaikilla menetelmillä on tiukat ennakkoehdot: luo palauttaa virheen, jos solmu on jo tehnyt luotu, poista tai setData – jos sitä ei vielä ole olemassa. Tarvitsimme joukkomenetelmän, jota voidaan kutsua ajattelematta avaimen läsnäoloa.

Yksi vaihtoehto on omaksua optimistinen lähestymistapa, kuten poistamisen kohdalla. Tarkista, onko solmu olemassa. Jos se on olemassa, kutsu setData, muussa tapauksessa luo. Jos viimeinen menetelmä palautti virheen, toista se uudelleen. Ensimmäinen huomioitava asia on, että olemassaolotesti on turha. Voit soittaa luomiseen välittömästi. Onnistunut suorittaminen tarkoittaa, että solmua ei ollut olemassa ja se luotiin. Muussa tapauksessa Create palauttaa asianmukaisen virheen, jonka jälkeen sinun on kutsuttava setData. Tietenkin puhelujen välillä kilpaileva puhelu voisi poistaa kärkipisteen, ja setData palauttaisi myös virheen. Tässä tapauksessa voit tehdä sen uudelleen, mutta onko se sen arvoista?

Jos molemmat menetelmät palauttavat virheen, tiedämme varmasti, että kilpaileva poisto tapahtui. Kuvitellaan, että tämä poisto tapahtui setin kutsumisen jälkeen. Silloin se merkitys, jota yritämme luoda, on jo pyyhitty pois. Tämä tarkoittaa, että voimme olettaa, että joukko suoritettiin onnistuneesti, vaikka todellisuudessa mitään ei kirjoitettu.

Lisää teknisiä tietoja

Tässä osiossa pidämme tauon hajautetuista järjestelmistä ja puhumme koodauksesta.
Yksi asiakkaan tärkeimmistä vaatimuksista oli cross-platform: vähintään yksi palveluista on tuettava Linuxissa, MacOS:ssa ja Windowsissa. Aluksi kehitimme vain Linuxille ja aloitimme testaamisen muilla järjestelmillä myöhemmin. Tämä aiheutti monia ongelmia, jotka olivat jonkin aikaa täysin epäselviä, miten niihin pitäisi suhtautua. Tämän seurauksena kaikkia kolmea koordinointipalvelua tuetaan nyt Linuxissa ja MacOS:ssa, kun taas vain Consul KV on tuettu Windowsissa.

Alusta alkaen yritimme käyttää valmiita kirjastoja palveluihin pääsemiseksi. ZooKeeperin tapauksessa valinta putosi ZooKeeper C++, jonka kääntäminen ei lopulta onnistunut Windowsissa. Tämä ei kuitenkaan ole yllättävää: kirjasto on sijoitettu vain linuxille. Konsulille oli ainoa vaihtoehto ppkonsuli. Siihen piti lisätä tukea istuntoja и liiketoimia. Etdd:lle ei löytynyt täysimittaista protokollan uusinta versiota tukevaa kirjastoa, joten yksinkertaisesti luotu grpc-asiakas.

ZooKeeper C++ -kirjaston asynkronisen käyttöliittymän innoittamana päätimme ottaa käyttöön myös asynkronisen käyttöliittymän. ZooKeeper C++ käyttää tähän tulevaisuuden/lupauksen primitiiviä. Valitettavasti ne toteutetaan STL:ssä hyvin vaatimattomasti. Esimerkiksi ei sitten menetelmä, joka soveltaa ohitettua funktiota tulevaisuuden tulokseen, kun se tulee saataville. Meidän tapauksessamme tällainen menetelmä on välttämätön tuloksen muuntamiseksi kirjastomme muotoon. Tämän ongelman kiertämiseksi meidän täytyi ottaa käyttöön oma yksinkertainen lankavaramme, koska asiakkaan pyynnöstä emme voineet käyttää raskaita kolmannen osapuolen kirjastoja, kuten Boostia.

Silloin toteutuksemme toimii näin. Kutsuttaessa luodaan ylimääräinen lupaus/tulevaisuus-pari. Uusi tulevaisuus palautetaan ja ohitettu sijoitetaan vastaavan funktion ja lisälupauksen kanssa jonoon. Säie poolista valitsee useita futuureja jonosta ja pollaa niitä käyttämällä wait_for-komentoa. Kun tulos tulee saataville, vastaava funktio kutsutaan ja sen palautusarvo välitetään lupaukselle.

Käytimme samaa säiepoolia kyselyjen suorittamiseen etcd:lle ja Consulille. Tämä tarkoittaa, että taustalla olevia kirjastoja voi käyttää useilla eri säikeillä. ppconsul ei ole lankaturvallinen, joten puhelut siihen on suojattu lukoilla.
Voit työskennellä grpc:n kanssa useista säikeistä, mutta siinä on hienouksia. etcd:ssä kellot toteutetaan grpc-virtojen kautta. Nämä ovat kaksisuuntaisia ​​kanavia tietyntyyppisille viesteille. Kirjasto luo yhden säikeen kaikille kelloille ja yhden säikeen, joka käsittelee saapuvat viestit. Joten grpc kieltää rinnakkaiset kirjoitukset streamiin. Tämä tarkoittaa, että kelloa alustaessasi tai poistaessasi sinun on odotettava, kunnes edellinen pyyntö on lähetetty, ennen kuin lähetät seuraavan. Käytämme synkronointiin ehdolliset muuttujat.

Koko

Katso itse: liboffkv.

Tiimimme: Raed Romanov, Ivan Glushenkov, Dmitri Kamaldinov, Viktor Krapivensky, Vitali Ivanin.

Lähde: will.com

Lisää kommentti