Kvin studentoj kaj tri distribuitaj ŝlosilvaloraj vendejoj

Aŭ kiel ni skribis klientan C++-bibliotekon por ZooKeeper, ktpd kaj Consul KV

En la mondo de distribuitaj sistemoj, ekzistas kelkaj tipaj taskoj: stoki informojn pri la konsisto de la areto, administri la agordon de nodoj, detekti misajn nodojn, elekti gvidanton. kaj aliaj. Por solvi ĉi tiujn problemojn, estas kreitaj specialaj distribuitaj sistemoj - kunordigaj servoj. Nun ni interesiĝos pri tri el ili: ZooKeeper, ktpd kaj Consul. El ĉiuj riĉaj funkcioj de Konsulo, ni koncentriĝos pri Consul KV.

Kvin studentoj kaj tri distribuitaj ŝlosilvaloraj vendejoj

En esenco, ĉiuj ĉi tiuj sistemoj estas mistoleremaj, linearizeblaj ŝlosilvaloraj vendejoj. Kvankam iliaj datummodeloj havas signifajn diferencojn, kiujn ni diskutos poste, ili solvas la samajn praktikajn problemojn. Evidente, ĉiu aplikaĵo, kiu uzas la kunordigan servon, estas ligita al unu el ili, kio povas konduki al la bezono subteni plurajn sistemojn en unu datumcentro, kiuj solvas la samajn problemojn por malsamaj aplikoj.

La ideo solvi ĉi tiun problemon ekestis en aŭstralia konsulta agentejo, kaj al ni, malgranda teamo de studentoj, devis efektivigi ĝin, pri kio mi parolos.

Ni sukcesis krei bibliotekon, kiu provizas komunan interfacon por labori kun ZooKeeper, ktpd kaj Consul KV. La biblioteko estas skribita en C++, sed estas planoj porti ĝin al aliaj lingvoj.

Datumaj Modeloj

Por disvolvi komunan interfacon por tri malsamaj sistemoj, vi devas kompreni, kion ili havas komune kaj kiel ili diferencas. Ni eltrovu ĝin.

ZooKeeper

Kvin studentoj kaj tri distribuitaj ŝlosilvaloraj vendejoj

La ŝlosiloj estas organizitaj en arbo kaj estas nomitaj nodoj. Sekve, por nodo vi povas ricevi liston de ĝiaj filoj. La operacioj krei znodon (krei) kaj ŝanĝi valoron (setData) estas apartigitaj: nur ekzistantaj ŝlosiloj povas esti legitaj kaj ŝanĝitaj. Horloĝoj povas esti alkroĉitaj al la operacioj kontroli la ekziston de nodo, legi valoron kaj ricevi infanojn. Horloĝo estas unufoja ellasilo, kiu pafas kiam la versio de la respondaj datumoj sur la servilo ŝanĝiĝas. Efemeraj nodoj estas uzataj por detekti fiaskojn. Ili estas ligitaj al la sesio de la kliento kiu kreis ilin. Kiam kliento fermas sesion aŭ ĉesas sciigi ZooKeeper pri ĝia ekzisto, ĉi tiuj nodoj estas aŭtomate forigitaj. Simplaj transakcioj estas subtenataj - aro da operacioj, kiuj aŭ ĉiuj sukcesas aŭ malsukcesas, se tio ne eblas por almenaŭ unu el ili.

ktp

Kvin studentoj kaj tri distribuitaj ŝlosilvaloraj vendejoj

La programistoj de ĉi tiu sistemo estis klare inspiritaj de ZooKeeper, kaj tial faris ĉion alimaniere. Ne ekzistas hierarkio de ŝlosiloj, sed ili formas leksikografie ordigitan aron. Vi povas akiri aŭ forigi ĉiujn ŝlosilojn apartenantaj al certa gamo. Ĉi tiu strukturo povas ŝajni stranga, sed ĝi estas fakte tre esprimplena, kaj hierarkia vidpunkto povas esti facile kopiita per ĝi.

etcd ne havas norman kompar-kaj-agordan operacion, sed ĝi havas ion pli bonan: transakcioj. Kompreneble, ili ekzistas en ĉiuj tri sistemoj, sed etcd-transakcioj estas precipe bonaj. Ili konsistas el tri blokoj: ĉeko, sukceso, malsukceso. La unua bloko enhavas aron da kondiĉoj, la dua kaj tria - operacioj. La transakcio estas efektivigita atome. Se ĉiuj kondiĉoj estas veraj, tiam la sukcesa bloko estas ekzekutita, alie la malsukcesa bloko estas ekzekutita. En API 3.3, sukcesaj kaj malsukcesaj blokoj povas enhavi nestitajn transakciojn. Tio estas, estas eble atome efektivigi kondiĉajn konstrukciojn de preskaŭ arbitra nesta nivelo. Vi povas lerni pli pri kio ĉekoj kaj operacioj ekzistas dokumentado.

Ankaŭ ĉi tie ekzistas horloĝoj, kvankam ili estas iom pli komplikaj kaj estas reuzeblaj. Tio estas, post instalo de horloĝo sur ŝlosila gamo, vi ricevos ĉiujn ĝisdatigojn en ĉi tiu gamo ĝis vi nuligos la horloĝon, kaj ne nur la unuan. En ktpd, la analogo de ZooKeeper-klientsesioj estas luoj.

Konsulo K.V.

Ankaŭ ne ekzistas strikta hierarkia strukturo ĉi tie, sed Konsulo povas krei la aspekton, ke ĝi ekzistas: vi povas akiri kaj forigi ĉiujn ŝlosilojn kun la specifita prefikso, tio estas, labori kun la "subarbo" de la ŝlosilo. Tiaj demandoj estas nomitaj rekursivaj. Krome, Konsulo povas elekti nur ŝlosilojn, kiuj ne enhavas la specifitan signon post la prefikso, kiu respondas al akiro de tujaj "infanoj". Sed indas memori, ke ĉi tio estas ĝuste la aspekto de hierarkia strukturo: estas tute eble krei ŝlosilon se ĝia gepatro ne ekzistas aŭ forigi ŝlosilon, kiu havas infanojn, dum la infanoj daŭre estos konservitaj en la sistemo.

Kvin studentoj kaj tri distribuitaj ŝlosilvaloraj vendejoj
Anstataŭ horloĝoj, Konsulo havas blokantajn HTTP-petojn. Esence, ĉi tiuj estas ordinaraj alvokoj al la metodo de legado de datumoj, por kiuj, kune kun aliaj parametroj, estas indikita la lasta konata versio de la datumoj. Se la aktuala versio de la respondaj datumoj sur la servilo estas pli granda ol la specifita, la respondo estas resendita tuj, alie - kiam la valoro ŝanĝiĝas. Ekzistas ankaŭ sesioj kiuj povas esti alkroĉitaj al ŝlosiloj iam ajn. Indas noti, ke male al etcd kaj ZooKeeper, kie forigo de sesioj kondukas al la forigo de rilataj ŝlosiloj, ekzistas reĝimo en kiu la sesio estas simple malligita de ili. Disponebla transakcioj, sen branĉoj, sed kun ĉiaj ĉekoj.

Kunmetante ĉion

ZooKeeper havas la plej rigoran datummodelon. La esprimaj gamodemandoj haveblaj en etcd ne povas esti efike kopiitaj en aŭ ZooKeeper aŭ Consul. Provante korpigi la plej bonan el ĉiuj servoj, ni finis kun interfaco preskaŭ ekvivalenta al la interfaco ZooKeeper kun la sekvaj gravaj esceptoj:

  • sekvenco, ujo kaj TTL-nodoj ne subtenata
  • ACL-oj ne estas subtenataj
  • la fiksita metodo kreas ŝlosilon se ĝi ne ekzistas (en ZK setData redonas eraron ĉi-kaze)
  • aro kaj cas-metodoj estas apartigitaj (en ZK ili estas esence la sama afero)
  • la viŝa metodo forigas nodon kune kun ĝia subarbo (en ZK forigo liveras eraron se la nodo havas filojn)
  • Por ĉiu ŝlosilo ekzistas nur unu versio - la valorversio (en ZK estas tri el ili)

La malakcepto de sinsekvaj nodoj ŝuldiĝas al la fakto, ke etcd kaj Consul ne havas enkonstruitan subtenon por ili, kaj ili povas esti facile efektivigitaj de la uzanto aldone al la rezulta biblioteka interfaco.

Efektivigi konduton similan al ZooKeeper dum forigo de vertico postulus konservi apartan infanan nombrilon por ĉiu ŝlosilo en etcd kaj Consul. Ĉar ni provis eviti stoki metainformojn, oni decidis forigi la tutan subarbon.

Subtilecoj de efektivigo

Ni rigardu pli detale kelkajn aspektojn de efektivigado de la biblioteka interfaco en malsamaj sistemoj.

Hierarkio en ktpd

Konservi hierarkian vidon en etcd montriĝis unu el la plej interesaj taskoj. Intervalo-demandoj faciligas preni liston de ŝlosiloj kun specifita prefikso. Ekzemple, se vi bezonas ĉion, kio komenciĝas "/foo", vi petas intervalon ["/foo", "/fop"). Sed ĉi tio redonus la tutan subarbon de la ŝlosilo, kiu eble ne estas akceptebla se la subarbo estas granda. Komence ni planis uzi ŝlosilan tradukmekanismon, efektivigita en zetcd. Ĝi implikas aldoni unu bajton komence de la ŝlosilo, egala al la profundo de la nodo en la arbo. Mi donu al vi ekzemplon.

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

Tiam ricevu ĉiujn tujajn filojn de la ŝlosilo "/foo" ebla petante gamon ["u02/foo/", "u02/foo0"). Jes, en ASCII "0" staras tuj poste "/".

Sed kiel efektivigi la forigon de vertico en ĉi tiu kazo? Rezultas, ke vi devas forigi ĉiujn gamojn de la tipo ["uXX/foo/", "uXX/foo0") por XX de 01 ĝis FF. Kaj tiam ni renkontis operacio nombro limo ene de unu transakcio.

Kiel rezulto, simpla ŝlosilkonverta sistemo estis inventita, kiu ebligis efike efektivigi kaj forigi ŝlosilon kaj akiri liston de infanoj. Sufiĉas aldoni specialan signon antaŭ la lasta signo. Ekzemple:

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

Poste forigante la ŝlosilon "/very" iĝas forigo "/u00very" kaj gamo ["/very/", "/very0"), kaj ricevi ĉiujn infanojn - en peto por ŝlosiloj el la gamo ["/very/u00", "/very/u01").

Forigante ŝlosilon en ZooKeeper

Kiel mi jam menciis, en ZooKeeper vi ne povas forigi nodon se ĝi havas infanojn. Ni volas forigi la ŝlosilon kune kun la subarbo. Kion mi devus fari? Ni faras tion kun optimismo. Unue, ni rekursie trairas la subarbon, akirante la filojn de ĉiu vertico kun aparta demando. Tiam ni konstruas transakcion kiu provas forigi ĉiujn nodojn de la subarbo en la ĝusta ordo. Kompreneble, ŝanĝoj povas okazi inter legado de subarbo kaj forigo de ĝi. En ĉi tiu kazo, la transakcio malsukcesos. Krome, la subarbo povas ŝanĝiĝi dum la legado. Peto por la filoj de la sekva nodo povas resendi eraron se, ekzemple, ĉi tiu nodo jam estis forigita. En ambaŭ kazoj, ni ripetas la tutan procezon denove.

Ĉi tiu aliro faras la forigon de ŝlosilo tre neefika se ĝi havas infanojn, kaj eĉ pli se la aplikaĵo daŭre funkcias kun la subarbo, forigante kaj kreante ŝlosilojn. Tamen tio permesis al ni eviti kompliki la efektivigon de aliaj metodoj en etcd kaj Consul.

metita en ZooKeeper

En ZooKeeper ekzistas apartaj metodoj, kiuj funkcias kun la arbostrukturo (create, delete, getChildren) kaj kiuj funkcias kun datumoj en nodoj (setData, getData).Cetere, ĉiuj metodoj havas striktajn antaŭkondiĉojn: create redonos eraron se la nodo jam havas. kreita, forigu aŭ starigisDatumojn - se ĝi ne jam ekzistas. Ni bezonis fiksitan metodon, kiu povas esti vokita sen pensi pri la ĉeesto de ŝlosilo.

Unu opcio estas preni optimisman aliron, kiel kun forigo. Kontrolu ĉu nodo ekzistas. Se ekzistas, voku setData, alie kreu. Se la lasta metodo redonis eraron, ripetu ĝin denove. La unua afero por noti estas ke la ekzista testo estas sencela. Vi povas tuj voki krei. Sukcesa kompletigo signifos, ke la nodo ne ekzistis kaj ĝi estis kreita. Alie, krei revenos la taŭgan eraron, post kio vi devas voki setData. Kompreneble, inter vokoj, vertico povus esti forigita per konkuranta voko, kaj setData ankaŭ resendus eraron. En ĉi tiu kazo, vi povas fari ĝin denove, sed ĉu ĝi valoras ĝin?

Se ambaŭ metodoj resendas eraron, tiam ni certe scias, ke konkuranta forigo okazis. Ni imagu, ke ĉi tiu forigo okazis post vokado de aro. Tiam kia ajn signifo ni provas establi estas jam forviŝita. Ĉi tio signifas, ke ni povas supozi, ke tiu aro estis ekzekutita sukcese, eĉ se fakte nenio estis skribita.

Pli teknikaj detaloj

En ĉi tiu sekcio ni ripozos de distribuitaj sistemoj kaj parolos pri kodado.
Unu el la ĉefaj postuloj de la kliento estis transplatforma: almenaŭ unu el la servoj devas esti subtenata en Linukso, MacOS kaj Vindozo. Komence, ni evoluigis nur por Linukso, kaj komencis testi sur aliaj sistemoj poste. Ĉi tio kaŭzis multajn problemojn, kiuj dum iom da tempo estis tute neklaraj kiel alproksimiĝi. Kiel rezulto, ĉiuj tri kunordigaj servoj nun estas subtenataj en Linukso kaj MacOS, dum nur Consul KV estas subtenata en Vindozo.

Ekde la komenco, ni provis uzi pretajn bibliotekojn por aliri servojn. En la kazo de ZooKeeper, la elekto falis ZooKeeper C++, kiu finfine malsukcesis kompili sur Vindozo. Ĉi tio tamen ne estas surpriza: la biblioteko estas poziciigita kiel nur linuksa. Por Konsulo la sola opcio estis ppkonsulo. Subteno devis esti aldonita al ĝi kunsidoj и transakcioj. Por etcd, plenrajta biblioteko subtenanta la lastan version de la protokolo ne estis trovita, do ni simple generita grpc-kliento.

Inspirite de la nesinkrona interfaco de la biblioteko ZooKeeper C++, ni decidis ankaŭ efektivigi nesinkronan interfacon. ZooKeeper C++ uzas estontajn/promesajn primitivulojn por tio. En STL, bedaŭrinde, ili estas efektivigitaj tre modeste. Ekzemple, ne tiam metodo, kiu aplikas la pasitan funkcion al la rezulto de la estonteco kiam ĝi iĝas havebla. En nia kazo, tia metodo estas necesa por konverti la rezulton al la formato de nia biblioteko. Por solvi ĉi tiun problemon, ni devis efektivigi nian propran simplan fadenan grupon, ĉar laŭ la peto de la kliento ni ne povis uzi pezajn triajn bibliotekojn kiel Boost.

Nia tiam efektivigo funkcias tiel. Kiam vokita, kroma promeso/estonta paro estas kreita. La nova estonteco estas resendita, kaj la pasita estas metita kune kun la responda funkcio kaj plia promeso en la atendovico. Fadeno de la naĝejo elektas plurajn estontecojn el la atendovico kaj sondas ilin uzante wait_for. Kiam rezulto fariĝas disponebla, la responda funkcio estas vokita kaj ĝia revenvaloro estas transdonita al la promeso.

Ni uzis la saman fadenan aron por efektivigi demandojn al etcd kaj Consul. Ĉi tio signifas, ke la subestaj bibliotekoj estas alireblaj per multoblaj malsamaj fadenoj. ppconsul ne estas fadena sekura, do alvokoj al ĝi estas protektitaj per seruroj.
Vi povas labori kun grpc de pluraj fadenoj, sed estas subtilecoj. En etcd horloĝoj estas efektivigitaj per grpc-riveretoj. Ĉi tiuj estas dudirektaj kanaloj por mesaĝoj de certa tipo. La biblioteko kreas ununuran fadenon por ĉiuj horloĝoj kaj ununuran fadenon kiu prilaboras envenantajn mesaĝojn. Do grpc malpermesas paralelajn skribojn al stream. Ĉi tio signifas, ke dum pravalorigo aŭ forigo de horloĝo, vi devas atendi ĝis la antaŭa peto finiĝos antaŭ sendi la sekvan. Ni uzas por sinkronigado kondiĉaj variabloj.

La rezulto

Vidu mem: liboffkv.

Nia teamo: Raed Romanov, Ivan Gluŝenkov, Dmitrij Kamaldinov, Viktoro Krapivensky, Vitalij Ivanin.

fonto: www.habr.com

Aldoni komenton