Päť študentov a tri distribuované obchody s hodnotou kľúča

Alebo ako sme napísali klientsku knižnicu C++ pre ZooKeeper, etcd a Consul KV

Vo svete distribuovaných systémov existuje množstvo typických úloh: ukladanie informácií o zložení klastra, správa konfigurácie uzlov, zisťovanie chybných uzlov, výber vedúceho a ďalšie. Na riešenie týchto problémov boli vytvorené špeciálne distribuované systémy - koordinačné služby. Teraz nás budú zaujímať tri z nich: ZooKeeper, etcd a Consul. Z celej bohatej funkcionality Consul sa zameriame na Consul KV.

Päť študentov a tri distribuované obchody s hodnotou kľúča

V podstate sú všetky tieto systémy odolné voči chybám, linearizovateľné úložiská hodnôt kľúča. Hoci ich dátové modely majú značné rozdiely, o ktorých si povieme neskôr, riešia rovnaké praktické problémy. Je zrejmé, že každá aplikácia, ktorá využíva koordinačnú službu, je viazaná na jednu z nich, čo môže viesť k potrebe podpory viacerých systémov v jednom dátovom centre, ktoré riešia rovnaké problémy pre rôzne aplikácie.

Myšlienka vyriešiť tento problém vznikla v jednej austrálskej poradenskej agentúre a implementovať ju pripadlo na nás, malý tím študentov, o čom budem hovoriť.

Podarilo sa nám vytvoriť knižnicu, ktorá poskytuje spoločné rozhranie pre prácu so ZooKeeper, etcd a Consul KV. Knižnica je napísaná v C++, ale plánuje sa jej portovanie do iných jazykov.

Dátové modely

Ak chcete vytvoriť spoločné rozhranie pre tri rôzne systémy, musíte pochopiť, čo majú spoločné a ako sa líšia. Poďme na to.

Ošetrovateľ v zoo

Päť študentov a tri distribuované obchody s hodnotou kľúča

Kľúče sú usporiadané do stromu a nazývajú sa uzly. V súlade s tým môžete pre uzol získať zoznam jeho potomkov. Operácie vytvorenia uzla (create) a zmeny hodnoty (setData) sú oddelené: čítať a meniť je možné iba existujúce kľúče. Hodinky môžu byť pripojené k operáciám kontroly existencie uzla, čítania hodnoty a získavania detí. Watch je jednorazový spúšťač, ktorý sa spustí, keď sa zmení verzia zodpovedajúcich údajov na serveri. Na detekciu porúch sa používajú efemérne uzly. Sú viazané na reláciu klienta, ktorý ich vytvoril. Keď klient zatvorí reláciu alebo prestane upozorňovať ZooKeeper na jej existenciu, tieto uzly sa automaticky odstránia. Podporované sú jednoduché transakcie – súbor operácií, ktoré buď všetky uspejú, alebo zlyhajú, ak to aspoň pre jednu z nich nie je možné.

atď

Päť študentov a tri distribuované obchody s hodnotou kľúča

Vývojári tohto systému sa jednoznačne inšpirovali ZooKeeperom, a preto urobili všetko inak. Neexistuje žiadna hierarchia kľúčov, ale tvoria lexikograficky usporiadanú množinu. Môžete získať alebo odstrániť všetky kľúče patriace do určitého rozsahu. Táto štruktúra sa môže zdať zvláštna, ale v skutočnosti je veľmi expresívna a možno cez ňu ľahko napodobniť hierarchický pohľad.

etcd nemá štandardnú operáciu porovnávania a nastavenia, ale má niečo lepšie: transakcie. Samozrejme, existujú vo všetkých troch systémoch, ale transakcie etcd sú obzvlášť dobré. Pozostávajú z troch blokov: kontrola, úspech, zlyhanie. Prvý blok obsahuje súbor podmienok, druhý a tretí - operácie. Transakcia sa vykonáva atomicky. Ak sú všetky podmienky splnené, potom sa vykoná blok úspechu, inak sa vykoná blok zlyhania. V API 3.3 môžu bloky úspechu a zlyhania obsahovať vnorené transakcie. To znamená, že je možné atómovo vykonávať podmienené konštrukcie takmer ľubovoľnej úrovne vnorenia. Môžete sa dozvedieť viac o tom, z čoho existujú kontroly a operácie dokumentáciu.

Hodinky existujú aj tu, aj keď sú trochu komplikovanejšie a sú opakovane použiteľné. To znamená, že po nainštalovaní hodiniek na kľúčový rozsah budete dostávať všetky aktualizácie v tomto rozsahu, až kým hodinky nezrušíte, a nielen tú prvú. V etcd sú analógom relácií klienta ZooKeeper prenájmy.

Konzul K.V.

Neexistuje tu ani prísna hierarchická štruktúra, ale Consul môže vytvoriť zdanie, že existuje: môžete získať a odstrániť všetky kľúče so zadanou predponou, to znamená pracovať s „podstromom“ kľúča. Takéto dopyty sa nazývajú rekurzívne. Okrem toho môže konzul vybrať iba kľúče, ktoré neobsahujú zadaný znak za predponou, čo zodpovedá získaniu bezprostredných „detí“. Je však potrebné pripomenúť, že toto je presne vzhľad hierarchickej štruktúry: je celkom možné vytvoriť kľúč, ak jeho rodič neexistuje, alebo odstrániť kľúč, ktorý má deti, zatiaľ čo deti budú naďalej uložené v systéme.

Päť študentov a tri distribuované obchody s hodnotou kľúča
Namiesto hodiniek má Consul blokovanie požiadaviek HTTP. V podstate ide o obyčajné volania metódy čítania dát, pri ktorých je spolu s ďalšími parametrami uvedená posledná známa verzia dát. Ak je aktuálna verzia zodpovedajúcich údajov na serveri väčšia ako zadaná, odpoveď sa vráti okamžite, inak - keď sa hodnota zmení. Existujú aj relácie, ktoré je možné kedykoľvek pripojiť ku kľúčom. Stojí za zmienku, že na rozdiel od etcd a ZooKeeper, kde odstránenie relácií vedie k vymazaniu pridružených kľúčov, existuje režim, v ktorom sa relácia jednoducho odpojí od nich. Dostupné transakcií, bez pobočiek, ale so všetkými druhmi kontrol.

Dávať to všetko dokopy

ZooKeeper má najprísnejší dátový model. Výrazné dotazy na rozsah dostupné v etcd nemožno efektívne napodobniť ani v ZooKeeper, ani v Consul. V snahe začleniť to najlepšie zo všetkých služieb sme skončili s rozhraním takmer rovnocenným s rozhraním ZooKeeper s nasledujúcimi významnými výnimkami:

  • sekvencia, kontajner a uzly TTL nie je podporované
  • ACL nie sú podporované
  • metóda set vytvorí kľúč, ak neexistuje (v ZK setData v tomto prípade vráti chybu)
  • metódy set a cas sú oddelené (v ZK sú v podstate to isté)
  • metóda erase vymaže uzol spolu s jeho podstromom (v ZK delete vráti chybu, ak má uzol potomkov)
  • Pre každý kľúč existuje len jedna verzia - hodnotová verzia (v ZK sú traja)

Odmietnutie sekvenčných uzlov je spôsobené tým, že etcd a Consul pre ne nemajú vstavanú podporu a používateľ ich môže jednoducho implementovať nad výsledné rozhranie knižnice.

Implementácia správania podobného ako ZooKeeper pri odstraňovaní vrcholu by vyžadovala udržiavanie samostatného podriadeného počítadla pre každý kľúč v etcd a Consul. Keďže sme sa snažili vyhnúť ukladaniu meta informácií, bolo rozhodnuté vymazať celý podstrom.

Jemnosť implementácie

Pozrime sa bližšie na niektoré aspekty implementácie knižničného rozhrania v rôznych systémoch.

Hierarchia v atď

Udržiavanie hierarchického pohľadu v etcd sa ukázalo ako jedna z najzaujímavejších úloh. Dotazy na rozsah uľahčujú získanie zoznamu kľúčov so zadanou predponou. Napríklad, ak potrebujete všetko, čo začína "/foo", žiadate o rozsah ["/foo", "/fop"). To by však vrátilo celý podstrom kľúča, čo nemusí byť prijateľné, ak je podstrom veľký. Najprv sme plánovali použiť kľúčový mechanizmus prekladu, implementované v zetcd. Zahŕňa pridanie jedného bajtu na začiatok kľúča, ktorý sa rovná hĺbke uzla v strome. Uvediem príklad.

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

Potom získajte všetky bezprostredné deti kľúča "/foo" možné vyžiadaním rozsahu ["u02/foo/", "u02/foo0"). Áno, v ASCII "0" stojí hneď po "/".

Ako však v tomto prípade implementovať odstránenie vrcholu? Ukazuje sa, že musíte odstrániť všetky rozsahy typu ["uXX/foo/", "uXX/foo0") pre XX od 01 do FF. A potom sme narazili limit počtu operácií v rámci jednej transakcie.

V dôsledku toho bol vynájdený jednoduchý systém konverzie kľúčov, ktorý umožnil efektívne implementovať vymazanie kľúča aj získanie zoznamu detí. Pred posledný žetón stačí pridať špeciálny znak. Napríklad:

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

Potom vymažte kľúč "/very" zmení na vymazanie "/u00very" a rozsah ["/very/", "/very0"), a získanie všetkých detí - v žiadosti o kľúče od sortimentu ["/very/u00", "/very/u01").

Odstránenie kľúča v ZooKeeper

Ako som už spomenul, v ZooKeeper nemôžete odstrániť uzol, ak má deti. Chceme odstrániť kľúč spolu s podstromom. Čo mám robiť? Robíme to s optimizmom. Najprv rekurzívne prechádzame podstrom a získavame potomkov každého vrcholu samostatným dotazom. Potom vytvoríme transakciu, ktorá sa pokúsi vymazať všetky uzly podstromu v správnom poradí. Samozrejme, medzi prečítaním podstromu a jeho odstránením môžu nastať zmeny. V tomto prípade transakcia zlyhá. Okrem toho sa podstrom môže počas procesu čítania meniť. Požiadavka na deti nasledujúceho uzla môže vrátiť chybu, ak napríklad tento uzol už bol vymazaný. V oboch prípadoch celý proces zopakujeme.

Tento prístup robí vymazanie kľúča veľmi neefektívnym, ak má deti, a ešte viac, ak aplikácia pokračuje v práci s podstromom, odstraňovaním a vytváraním kľúčov. To nám však umožnilo vyhnúť sa komplikáciám pri implementácii iných metód v etcd a Consul.

nastaviť v ZooKeeper

V ZooKeeper existujú samostatné metódy, ktoré pracujú so stromovou štruktúrou (create, delete, getChildren) a ktoré pracujú s údajmi v uzloch (setData, getData) Navyše všetky metódy majú prísne podmienky: create vráti chybu, ak uzol už boli vytvorené, vymazané alebo setData – ak ešte neexistujú. Potrebovali sme nastavenú metódu, ktorú možno volať bez toho, aby sme premýšľali o prítomnosti kľúča.

Jednou z možností je zaujať optimistický prístup, ako pri vypúšťaní. Skontrolujte, či existuje uzol. Ak existuje, zavolajte setData, inak vytvorte. Ak posledná metóda vrátila chybu, zopakujte to znova. Prvá vec, ktorú treba poznamenať, je, že test existencie je zbytočný. Môžete okamžite zavolať vytvorenie. Úspešné dokončenie bude znamenať, že uzol neexistoval a bol vytvorený. V opačnom prípade Create vráti príslušnú chybu, po ktorej musíte zavolať setData. Samozrejme, medzi hovormi by mohol byť vrchol vymazaný konkurenčným volaním a setData by tiež vrátilo chybu. V tomto prípade to môžete urobiť znova, ale stojí to za to?

Ak obe metódy vrátia chybu, potom s istotou vieme, že došlo ku konkurenčnému vymazaniu. Predstavme si, že k tomuto vymazaniu došlo po volaní setu. Potom je akýkoľvek význam, ktorý sa snažíme nastoliť, už vymazaný. To znamená, že môžeme predpokladať, že set bol úspešne vykonaný, aj keď v skutočnosti nebolo nič zapísané.

Viac technických detailov

V tejto časti si dáme pauzu od distribuovaných systémov a povieme si niečo o kódovaní.
Jednou z hlavných požiadaviek zákazníka bola multiplatformnosť: aspoň jedna zo služieb musí byť podporovaná na Linuxe, MacOS a Windows. Spočiatku sme vyvíjali iba pre Linux a neskôr sme začali testovať na iných systémoch. Spôsobilo to množstvo problémov, ku ktorým bolo nejaký čas úplne nejasné, ako k nim pristupovať. Výsledkom je, že všetky tri koordinačné služby sú teraz podporované v systémoch Linux a MacOS, zatiaľ čo v systéme Windows je podporovaný iba Consul KV.

Na prístup k službám sme sa od začiatku snažili využívať hotové knižnice. V prípade ZooKeepera padla voľba ZooKeeper C++, ktorý sa nakoniec nepodarilo skompilovať na Windows. To však nie je prekvapujúce: knižnica je umiestnená ako linuxová. Pre konzula bola jediná možnosť ppkonzul. K tomu bolo treba pridať podporu zasadania и transakcií. Pre etcd sa nenašla plnohodnotná knižnica podporujúca najnovšiu verziu protokolu, tak sme jednoducho vygenerovaný klient grpc.

Inšpirovaní asynchrónnym rozhraním knižnice ZooKeeper C++ sme sa rozhodli implementovať aj asynchrónne rozhranie. ZooKeeper C++ na to používa primitívy budúcnosti/sľubov. V STL sú, žiaľ, implementované veľmi skromne. Napríklad nie potom metóda, ktorý použije odovzdanú funkciu na výsledok budúcnosti, keď bude k dispozícii. V našom prípade je takáto metóda nevyhnutná na prevod výsledku do formátu našej knižnice. Aby sme tento problém obišli, museli sme implementovať vlastný jednoduchý fond vlákien, pretože na žiadosť zákazníka sme nemohli použiť ťažké knižnice tretích strán, ako je Boost.

Naša vtedajšia implementácia funguje takto. Po zavolaní sa vytvorí ďalší pár prísľub/budúcnosť. Nová budúcnosť sa vráti a odovzdaná budúcnosť sa umiestni spolu s príslušnou funkciou a dodatočným prísľubom do frontu. Vlákno z oblasti vyberie niekoľko futures z frontu a vyzve ich pomocou wait_for. Keď je výsledok dostupný, zavolá sa zodpovedajúca funkcia a jej návratová hodnota sa odovzdá prísľubu.

Použili sme rovnaký súbor vlákien na vykonávanie dopytov na etcd a Consul. To znamená, že k základným knižniciam môže pristupovať viacero rôznych vlákien. ppconsul nie je bezpečný pre vlákna, takže volania naň sú chránené zámkami.
S grpc môžete pracovať z viacerých vlákien, existujú však jemnosti. V etcd sú hodinky implementované cez grpc streamy. Sú to obojsmerné kanály pre správy určitého typu. Knižnica vytvorí jedno vlákno pre všetky hodinky a jedno vlákno, ktoré spracováva prichádzajúce správy. Takže grpc zakazuje paralelné zápisy do streamu. To znamená, že pri inicializácii alebo odstraňovaní hodiniek musíte pred odoslaním ďalšej počkať, kým predchádzajúca požiadavka nedokončí odoslanie. Používame na synchronizáciu podmienené premenné.

Celkový

Presvedčte sa sami: liboffkv.

Náš tím: Raed Romanov, Ivan Glušenkov, Dmitrij Kamaldinov, Viktor Krapivenskij, Vitalij Ivanin.

Zdroj: hab.com

Pridať komentár