Пет ученика и три дистрибуиране продавнице кључ/вредност

Или како смо написали клијентску Ц++ библиотеку за ЗооКеепер, етцд и Цонсул КВ

У свету дистрибуираних система постоји низ типичних задатака: чување информација о саставу кластера, управљање конфигурацијом чворова, откривање неисправних чворова, избор лидера и други. За решавање ових проблема створени су посебни дистрибуирани системи – службе за координацију. Сада ће нас занимати три од њих: ЗооКеепер, етцд и Цонсул. Од свих богатих функционалности Конзула, фокусираћемо се на Цонсул КВ.

Пет ученика и три дистрибуиране продавнице кључ/вредност

У суштини, сви ови системи су отпорна на грешке и линеаризабилна складишта кључ/вредност. Иако њихови модели података имају значајне разлике, о чему ћемо касније говорити, они решавају исте практичне проблеме. Очигледно је да је свака апликација која користи услугу координације везана за једну од њих, што може довести до потребе да се у једном дата центру подржи више система који решавају исте проблеме за различите апликације.

Идеја да се овај проблем реши потекла је у једној аустралијској консултантској агенцији, а на нама, малом тиму студената, пало је да то спроведемо, о чему ћу и да причам.

Успели смо да направимо библиотеку која обезбеђује заједнички интерфејс за рад са ЗооКеепером, етцд и Цонсул КВ. Библиотека је написана на Ц++, али постоје планови да се пренесе на друге језике.

Модели података

Да бисте развили заједнички интерфејс за три различита система, морате разумети шта им је заједничко и по чему се разликују. Хајде да то схватимо.

ЗооКеепер

Пет ученика и три дистрибуиране продавнице кључ/вредност

Кључеви су организовани у стабло и зову се чворови. Сходно томе, за чвор можете добити листу његове деце. Операције креирања зноде-а (цреате) и промене вредности (сетДата) су одвојене: само постојећи кључеви се могу читати и мењати. Сатови се могу прикључити операцијама провере постојања чвора, читања вредности и добијања деце. Ватцх је једнократни окидач који се покреће када се промени верзија одговарајућих података на серверу. Ефемерни чворови се користе за откривање кварова. Они су везани за сесију клијента који их је креирао. Када клијент затвори сесију или престане да обавештава ЗооКеепер о постојању, ови чворови се аутоматски бришу. Подржане су једноставне трансакције – скуп операција које или успеју или не успеју ако то није могуће за бар једну од њих.

итдд

Пет ученика и три дистрибуиране продавнице кључ/вредност

Програмери овог система су били очигледно инспирисани ЗооКеепер-ом, и стога су све урадили другачије. Не постоји хијерархија кључева, већ они чине лексикографски уређен скуп. Можете добити или избрисати све кључеве који припадају одређеном опсегу. Ова структура може изгледати чудно, али је заправо врло експресивна и кроз њу се лако може опонашати хијерархијски поглед.

етцд нема стандардну операцију упоређивања и постављања, али има нешто боље: трансакције. Наравно, постоје у сва три система, али су етцд трансакције посебно добре. Састоје се од три блока: провера, успех, неуспех. Први блок садржи скуп услова, други и трећи - операције. Трансакција се извршава атомски. Ако су сви услови тачни, онда се извршава блок успеха, у супротном се извршава блок неуспеха. У АПИ-ју 3.3, блокови успеха и неуспеха могу да садрже угнежђене трансакције. То јест, могуће је атомски извршавати условне конструкције скоро произвољног нивоа гнежђења. Можете сазнати више о томе од чега постоје провере и операције документација.

Сатови постоје и овде, иако су мало компликованији и за вишекратну употребу. Односно, након инсталирања сата на опсег кључева, добићете сва ажурирања у овом опсегу док не откажете сат, а не само први. У етцд-у, аналогни клијентским сесијама ЗооКеепер-а су закупи.

Конзул К.В.

Овде такође не постоји строга хијерархијска структура, али Цонсул може да створи изглед да постоји: можете добити и избрисати све кључеве са наведеним префиксом, односно радити са „подстаблом“ кључа. Такви упити се називају рекурзивни. Поред тога, Конзул може да изабере само кључеве који не садрже наведени знак после префикса, што одговара непосредном добијању „деце“. Али вреди запамтити да је то управо изглед хијерархијске структуре: сасвим је могуће креирати кључ ако његов родитељ не постоји или избрисати кључ који има потомке, док ће деца и даље бити ускладиштена у систему.

Пет ученика и три дистрибуиране продавнице кључ/вредност
Уместо сатова, Цонсул има блокирање ХТТП захтева. У суштини, то су обични позиви методи читања података, за које је, уз остале параметре, назначена последња позната верзија података. Ако је тренутна верзија одговарајућих података на серверу већа од наведене, одговор се враћа одмах, у супротном - када се вредност промени. Постоје и сесије које се могу приложити кључевима у било ком тренутку. Вреди напоменути да за разлику од етцд-а и ЗооКеепер-а, где брисање сесија доводи до брисања повезаних кључева, постоји начин у коме се сесија једноставно не повезује са њима. Доступан трансакције, без филијала, али са свим врстама провера.

Све састављање

ЗооКеепер има најригорознији модел података. Експресивни упити опсега доступни у етцд-у не могу се ефикасно емулирати ни у ЗооКеепер-у ни у Цонсул-у. Покушавајући да уградимо најбоље од свих услуга, на крају смо добили интерфејс који је скоро еквивалентан интерфејсу ЗооКеепер са следећим значајним изузецима:

  • секвенца, контејнер и ТТЛ чворови није подржан
  • АЦЛ-ови нису подржани
  • сет метода креира кључ ако не постоји (у ЗК сетДата враћа грешку у овом случају)
  • сет и цас методе су одвојене (у ЗК су у суштини иста ствар)
  • метода ерасе брише чвор заједно са његовим подстаблом (у ЗК делете враћа грешку ако чвор има потомке)
  • За сваки кључ постоји само једна верзија - верзија вредности (у ЗК има их три)

Одбијање секвенцијалних чворова је због чињенице да етцд и Цонсул немају уграђену подршку за њих, а корисник их може лако имплементирати на врху резултујућег интерфејса библиотеке.

Имплементација понашања сличног ЗооКеепер-у приликом брисања темена захтевало би одржавање засебног бројача деце за сваки кључ у етцд и Цонсул. Пошто смо покушали да избегнемо складиштење мета информација, одлучено је да се избрише цело подстабло.

Суптилности имплементације

Хајде да ближе погледамо неке аспекте имплементације интерфејса библиотеке у различитим системима.

Хијерархија у итд

Одржавање хијерархијског погледа у етцд-у се показало као један од најзанимљивијих задатака. Упити за опсег олакшавају преузимање листе кључева са наведеним префиксом. На пример, ако вам треба све што почиње са "/foo", тражите опсег ["/foo", "/fop"). Али ово би вратило цело подстабло кључа, што можда није прихватљиво ако је подстабло велико. У почетку смо планирали да користимо кључни механизам за превођење, имплементиран у зетцд. Укључује додавање једног бајта на почетак кључа, једнако дубини чвора у стаблу. Дозволите ми да вам дам пример.

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

Затим узмите сву непосредну децу кључа "/foo" могуће захтевом за опсег ["u02/foo/", "u02/foo0"). Да, у АСЦИИ "0" стоји одмах после "/".

Али како имплементирати уклањање темена у овом случају? Испоставило се да морате избрисати све опсеге типа ["uXX/foo/", "uXX/foo0") за КСКС од 01 до ФФ. А онда смо налетели ограничење броја операција у оквиру једне трансакције.

Као резултат тога, измишљен је једноставан систем конверзије кључева, који је омогућио ефикасну имплементацију и брисања кључа и добијања листе деце. Довољно је додати посебан знак пре последњег токена. На пример:

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

Затим брисање кључа "/very" прелази у брисање "/u00very" и домет ["/very/", "/very0"), и добијање свих деце - у захтеву за кључеве из опсега ["/very/u00", "/very/u01").

Уклањање кључа у ЗооКеепер-у

Као што сам већ поменуо, у ЗооКеепер-у не можете избрисати чвор ако има децу. Желимо да избришемо кључ заједно са подстаблом. Шта бих требао да урадим? Ово радимо са оптимизмом. Прво, рекурзивно прелазимо кроз подстабло, добијајући потомке сваког темена са посебним упитом. Затим градимо трансакцију која покушава да избрише све чворове подстабла у исправном редоследу. Наравно, може доћи до промена између читања подстабла и брисања. У овом случају трансакција неће успети. Штавише, подстабло се може променити током процеса читања. Захтев за потомство следећег чвора може да врати грешку ако је, на пример, овај чвор већ обрисан. У оба случаја цео процес понављамо поново.

Овај приступ чини брисање кључа веома неефикасним ако има потомке, а још више ако апликација настави да ради са подстаблом, брисањем и креирањем кључева. Међутим, ово нам је омогућило да избегнемо компликовање примене других метода у етцд и Цонсул.

постављен у ЗооКеепер

У ЗооКеепер-у постоје одвојене методе које раде са структуром стабла (цреате, делете, гетЦхилдрен) и које раде са подацима у чворовима (сетДата, гетДата). Штавише, све методе имају строге предуслове: цреате ће вратити грешку ако је чвор већ имао је креиран, избришите или сетДата – ако већ не постоји. Требао нам је сет метод који се може позвати без размишљања о присуству кључа.

Једна опција је да се заузме оптимистичан приступ, као код брисања. Проверите да ли чвор постоји. Ако постоји, позовите сетДата, у супротном креирајте. Ако је последњи метод вратио грешку, поновите све изнова. Прва ствар коју треба приметити је да је тест постојања бесмислен. Можете одмах позвати креирање. Успешан завршетак ће значити да чвор није постојао и да је креиран. У супротном, цреате ће вратити одговарајућу грешку, након чега морате позвати сетДата. Наравно, између позива, врх може бити обрисан конкурентским позивом, а сетДата би такође вратио грешку. У овом случају, можете то учинити изнова, али да ли је вредно тога?

Ако обе методе врате грешку, онда сигурно знамо да је дошло до конкурентског брисања. Замислимо да се ово брисање догодило након позивања скупа. Онда је оно значење које покушавамо да успоставимо већ избрисано. То значи да можемо претпоставити да је скуп успешно извршен, чак и ако у ствари ништа није написано.

Више техничких детаља

У овом одељку ћемо направити паузу од дистрибуираних система и причати о кодирању.
Један од главних захтева корисника био је вишеплатформа: бар једна од услуга мора бити подржана на Линук-у, МацОС-у и Виндовс-у. У почетку смо развијали само за Линук, а касније смо почели да тестирамо на другим системима. То је изазвало много проблема, којима је неко време било потпуно нејасно како приступити. Као резултат тога, све три услуге координације су сада подржане на Линук-у и МацОС-у, док је само Цонсул КВ подржан на Виндовс-у.

Од самог почетка смо се трудили да користимо готове библиотеке за приступ сервисима. У случају ЗооКеепер-а, избор је пао ЗооКеепер Ц++, који на крају није успео да се компајлира на Виндовс-у. Ово, међутим, није изненађујуће: библиотека је позиционирана као само за Линук. За Конзула је једина опција била ппцонсул. Требало је томе додати подршку седнице и трансакције. За етцд, пуноправна библиотека која подржава најновију верзију протокола није пронађена, па смо једноставно генерисани грпц клијент.

Инспирисани асинхроним интерфејсом ЗооКеепер Ц++ библиотеке, одлучили смо да имплементирамо и асинхрони интерфејс. ЗооКеепер Ц++ за ово користи будуће/промисе примитиве. У СТЛ-у се, нажалост, примењују веома скромно. На пример, не затим метод, који примењује прослеђену функцију на резултат будућности када постане доступна. У нашем случају, такав метод је неопходан за претварање резултата у формат наше библиотеке. Да бисмо заобишли овај проблем, морали смо да имплементирамо сопствени једноставан скуп нити, пошто на захтев купца нисмо могли да користимо тешке библиотеке трећих страна као што је Боост.

Наша тадашња имплементација функционише овако. Када се позове, креира се додатни пар обећање/будуће. Нова будућност се враћа, а прослеђена се ставља заједно са одговарајућом функцијом и додатним обећањем у ред чекања. Нит из скупа бира неколико фјучерса из реда и испитује их користећи ваит_фор. Када резултат постане доступан, позива се одговарајућа функција и њена повратна вредност се прослеђује обећању.

Користили смо исти скуп нити за извршавање упита за етцд и Цонсул. То значи да основним библиотекама може приступити више различитих нити. ппцонсул није сигуран нити, тако да су позиви на њега заштићени бравама.
Можете радити са грпц-ом из више нити, али постоје суптилности. У етцд сатови се имплементирају преко грпц стреамова. То су двосмерни канали за поруке одређеног типа. Библиотека креира једну нит за све сатове и једну нит која обрађује долазне поруке. Дакле, грпц забрањује паралелно уписивање у стриминг. То значи да када иницијализујете или бришете сат, морате сачекати да се претходни захтев заврши слањем пре него што пошаљете следећи. Користимо за синхронизацију условне променљиве.

Укупан

Уверите се и сами: либоффкв.

Наш тим: Раед Романов, Иван Глушенков, Дмитриј Камалдинов, Виктор Крапивенски, Виталиј Иванин.

Извор: ввв.хабр.цом

Додај коментар