Beshta talaba va uchta taqsimlangan kalit-qiymat do'konlari

Yoki ZooKeeper, etcd va Consul KV uchun mijoz C++ kutubxonasini qanday yozdik

Tarqalgan tizimlar dunyosida bir qator tipik vazifalar mavjud: klaster tarkibi haqida ma'lumotni saqlash, tugunlar konfiguratsiyasini boshqarish, noto'g'ri tugunlarni aniqlash, etakchi tanlash va boshqalar. Ushbu muammolarni hal qilish uchun maxsus taqsimlangan tizimlar - muvofiqlashtirish xizmatlari yaratilgan. Endi biz ulardan uchtasi bilan qiziqamiz: ZooKeeper, etcd va Consul. Konsulning barcha boy funksiyalaridan biz Konsul KVga e'tibor qaratamiz.

Beshta talaba va uchta taqsimlangan kalit-qiymat do'konlari

Aslini olganda, bu tizimlarning barchasi xatoga chidamli, chiziqli kalit-qiymat omborlaridir. Garchi ularning ma'lumotlar modellari sezilarli farqlarga ega bo'lsa-da, ularni keyinroq muhokama qilamiz, ular bir xil amaliy muammolarni hal qilishadi. Shubhasiz, muvofiqlashtirish xizmatidan foydalanadigan har bir dastur ulardan biriga bog'langan, bu esa turli ilovalar uchun bir xil muammolarni hal qiladigan bir ma'lumot markazida bir nechta tizimlarni qo'llab-quvvatlash zarurligiga olib kelishi mumkin.

Ushbu muammoni hal qilish g'oyasi avstraliyalik konsalting agentligida paydo bo'lgan va uni amalga oshirish bizga, kichik talabalar jamoasiga tushdi, men bu haqda gaplashmoqchiman.

Biz ZooKeeper, etcd va Consul KV bilan ishlash uchun umumiy interfeysni ta'minlovchi kutubxona yaratishga muvaffaq bo'ldik. Kutubxona C++ tilida yozilgan, lekin uni boshqa tillarga o‘tkazish rejalashtirilgan.

Ma'lumotlar modellari

Uch xil tizimlar uchun umumiy interfeysni ishlab chiqish uchun ularning umumiyligi va ular qanday farq qilishini tushunishingiz kerak. Keling, buni aniqlaylik.

ZooKeeper

Beshta talaba va uchta taqsimlangan kalit-qiymat do'konlari

Kalitlar daraxt shaklida tashkil etilgan va tugunlar deb ataladi. Shunga ko'ra, tugun uchun siz uning bolalari ro'yxatini olishingiz mumkin. Znode yaratish (yaratish) va qiymatni o'zgartirish (setData) operatsiyalari ajratilgan: faqat mavjud kalitlarni o'qish va o'zgartirish mumkin. Soatlar tugunning mavjudligini tekshirish, qiymatni o'qish va bolalarni olish operatsiyalariga biriktirilishi mumkin. Watch - bu serverdagi tegishli ma'lumotlarning versiyasi o'zgarganda ishga tushadigan bir martalik tetik. Efemer tugunlari nosozliklarni aniqlash uchun ishlatiladi. Ular ularni yaratgan mijozning sessiyasiga bog'langan. Mijoz sessiyani yopsa yoki ZooKeeperga uning mavjudligi haqida xabar berishni to'xtatsa, bu tugunlar avtomatik ravishda o'chiriladi. Oddiy tranzaktsiyalar qo'llab-quvvatlanadi - agar ulardan kamida bittasi uchun buning iloji bo'lmasa, barchasi muvaffaqiyatli yoki muvaffaqiyatsiz bo'lgan operatsiyalar to'plami.

va boshqalar

Beshta talaba va uchta taqsimlangan kalit-qiymat do'konlari

Ushbu tizimni ishlab chiquvchilar ZooKeeper-dan aniq ilhomlangan va shuning uchun hamma narsani boshqacha qilishgan. Kalitlar ierarxiyasi yo'q, lekin ular leksikografik jihatdan tartiblangan to'plamni tashkil qiladi. Siz ma'lum bir diapazonga tegishli barcha kalitlarni olishingiz yoki o'chirishingiz mumkin. Ushbu tuzilma g'alati tuyulishi mumkin, lekin u aslida juda ifodali va ierarxik ko'rinishni osongina taqlid qilish mumkin.

etcd standart solishtirish va o'rnatish operatsiyasiga ega emas, lekin unda yaxshiroq narsa bor: tranzaktsiyalar. Albatta, ular uchta tizimda ham mavjud, ammo etcd operatsiyalari ayniqsa yaxshi. Ular uchta blokdan iborat: tekshirish, muvaffaqiyat, muvaffaqiyatsizlik. Birinchi blokda shartlar to'plami, ikkinchi va uchinchi - operatsiyalar mavjud. Tranzaktsiya atomik tarzda amalga oshiriladi. Agar barcha shartlar to'g'ri bo'lsa, u holda muvaffaqiyat bloki bajariladi, aks holda muvaffaqiyatsiz blok bajariladi. API 3.3 da muvaffaqiyat va muvaffaqiyatsizlik bloklari ichki o'rnatilgan tranzaktsiyalarni o'z ichiga olishi mumkin. Ya'ni, deyarli o'zboshimchalik darajasidagi shartli konstruktsiyalarni atomik tarzda bajarish mumkin. Qaysi tekshiruvlar va operatsiyalar mavjudligi haqida ko'proq bilib olishingiz mumkin hujjatlar.

Bu erda ham soatlar mavjud, garchi ular biroz murakkabroq va qayta foydalanish mumkin. Ya'ni, soatni kalit diapazoniga o'rnatganingizdan so'ng, siz faqat birinchisini emas, balki soatni bekor qilguningizcha ushbu diapazondagi barcha yangilanishlarni olasiz. etcd da, ZooKeeper mijoz seanslarining analogi ijara hisoblanadi.

Konsul K.V.

Bu erda qat'iy ierarxik tuzilma ham yo'q, lekin konsul mavjud ko'rinishni yaratishi mumkin: siz ko'rsatilgan prefiksli barcha kalitlarni olishingiz va o'chirishingiz mumkin, ya'ni kalitning "pastki daraxti" bilan ishlashingiz mumkin. Bunday so'rovlar rekursiv deyiladi. Bundan tashqari, konsul faqat prefiksdan keyin ko'rsatilgan belgini o'z ichiga olmaydigan kalitlarni tanlashi mumkin, bu darhol "bolalar" ni olishga mos keladi. Ammo shuni esda tutish kerakki, bu aniq ierarxik tuzilmaning ko'rinishi: agar uning ota-onasi mavjud bo'lmasa, kalitni yaratish yoki bolalari bo'lgan kalitni o'chirish, bolalar esa tizimda saqlanishda davom etishi mumkin.

Beshta talaba va uchta taqsimlangan kalit-qiymat do'konlari
Soatlar o'rniga konsul HTTP so'rovlarini bloklaydi. Aslida, bu ma'lumotlarni o'qish usuliga oddiy qo'ng'iroqlar bo'lib, ular uchun boshqa parametrlar bilan bir qatorda ma'lumotlarning oxirgi ma'lum versiyasi ko'rsatilgan. Agar serverdagi tegishli ma'lumotlarning joriy versiyasi belgilanganidan kattaroq bo'lsa, javob darhol qaytariladi, aks holda - qiymat o'zgarganda. Istalgan vaqtda kalitlarga biriktirilishi mumkin bo'lgan seanslar ham mavjud. Shuni ta'kidlash kerakki, etcd va ZooKeeper-dan farqli o'laroq, seanslarni o'chirish bog'langan kalitlarni o'chirishga olib keladi, seans ular bilan oddiygina ajratilgan rejim mavjud. Mavjud operatsiyalar, filiallarsiz, lekin barcha turdagi cheklar bilan.

Hammasini bir joyga qo'yish

ZooKeeper eng qat'iy ma'lumotlar modeliga ega. etcd-da mavjud bo'lgan ekspressiv diapazon so'rovlarini ZooKeeper yoki Konsulda samarali tarzda taqlid qilib bo'lmaydi. Barcha xizmatlardan eng yaxshisini birlashtirishga harakat qilib, biz ZooKeeper interfeysiga deyarli teng keladigan interfeysga ega bo'ldik va quyidagi muhim istisnolar mavjud:

  • ketma-ketlik, konteyner va TTL tugunlari qo'llab-quvvatlanmaydi
  • ACL qo'llab-quvvatlanmaydi
  • to'siq usuli, agar u mavjud bo'lmasa, kalit yaratadi (ZKda setData bu holatda xatoni qaytaradi)
  • set va cas usullari ajratilgan (ZKda ular bir xil narsadir)
  • o'chirish usuli tugunni pastki daraxti bilan birga o'chiradi (ZKda Delete tugunning bolalari bo'lsa, xatoni qaytaradi)
  • Har bir kalit uchun faqat bitta versiya mavjud - qiymat versiyasi (ZK ulardan uchtasi bor)

Ketma-ket tugunlarning rad etilishi, etcd va Consul-da ular uchun o'rnatilgan yordam yo'qligi bilan bog'liq va ular foydalanuvchi tomonidan yaratilgan kutubxona interfeysi ustida osongina amalga oshirilishi mumkin.

Cho'qqini o'chirishda ZooKeeperga o'xshash xatti-harakatni amalga oshirish etcd va Konsuldagi har bir kalit uchun alohida bolalar hisoblagichini saqlashni talab qiladi. Meta-ma'lumotni saqlashdan qochishga harakat qilganimiz sababli, butun pastki daraxtni o'chirishga qaror qilindi.

Amalga oshirishning nozik tomonlari

Keling, turli tizimlarda kutubxona interfeysini amalga oshirishning ba'zi jihatlarini batafsil ko'rib chiqaylik.

Va hokazolarda ierarxiya

etcd da ierarxik ko'rinishni saqlab qolish eng qiziqarli vazifalardan biri bo'lib chiqdi. Diapazon so'rovlari belgilangan prefiksli kalitlar ro'yxatini olishni osonlashtiradi. Misol uchun, agar siz bilan boshlangan hamma narsa kerak bo'lsa "/foo", siz diapazonni so'rayapsiz ["/foo", "/fop"). Ammo bu kalitning butun pastki daraxtini qaytaradi, agar pastki daraxt katta bo'lsa, bu qabul qilinishi mumkin emas. Avvaliga biz kalit tarjima mexanizmidan foydalanishni rejalashtirgan edik, zetcd da amalga oshirilgan. Bu kalitning boshiga daraxtdagi tugun chuqurligiga teng bo'lgan bir bayt qo'shishni o'z ichiga oladi. Sizga bir misol keltiraman.

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

Keyin kalitning barcha bevosita bolalarini oling "/foo" diapazonni so'rash orqali mumkin ["u02/foo/", "u02/foo0"). Ha, ASCII da "0" darhol keyin turadi "/".

Ammo bu holda cho'qqini olib tashlashni qanday amalga oshirish kerak? Ma'lum bo'lishicha, siz barcha turdagi diapazonlarni o'chirishingiz kerak ["uXX/foo/", "uXX/foo0") XX uchun 01 dan FF gacha. Va keyin biz yugurdik operatsiya raqami chegarasi bitta tranzaksiya doirasida.

Natijada, kalitni o'chirish va bolalar ro'yxatini olishni samarali amalga oshirish imkonini beradigan oddiy kalit konvertatsiya tizimi ixtiro qilindi. Oxirgi belgidan oldin maxsus belgi qo'shish kifoya. Masalan:

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

Keyin kalit o'chiriladi "/very" oʻchirishga aylanadi "/u00very" va diapazon ["/very/", "/very0"), va barcha bolalarni olish - diapazondagi kalitlarga so'rovda ["/very/u00", "/very/u01").

ZooKeeper-da kalitni olib tashlash

Yuqorida aytib o'tganimdek, ZooKeeper-da siz tugunni o'chira olmaysiz, agar uning bolalari bo'lsa. Biz kalitni pastki daraxt bilan birga o'chirmoqchimiz. Nima qilishim kerak? Biz buni optimizm bilan qilamiz. Birinchidan, biz pastki daraxt bo'ylab rekursiv ravishda aylanib o'tamiz, har bir cho'qqining bolalarini alohida so'rov bilan olamiz. Keyin biz pastki daraxtning barcha tugunlarini to'g'ri tartibda o'chirishga harakat qiladigan tranzaktsiyani quramiz. Albatta, pastki daraxtni o'qish va uni o'chirish o'rtasida o'zgarishlar bo'lishi mumkin. Bunday holda, tranzaktsiya muvaffaqiyatsiz bo'ladi. Bundan tashqari, o'qish jarayonida pastki daraxt o'zgarishi mumkin. Keyingi tugunning bolalari uchun so'rov, masalan, ushbu tugun allaqachon o'chirilgan bo'lsa, xatoni qaytarishi mumkin. Ikkala holatda ham biz butun jarayonni yana takrorlaymiz.

Ushbu yondashuv, agar uning bolalari bo'lsa, kalitni o'chirish juda samarasiz bo'ladi va undan ham ko'proq, agar dastur pastki daraxt bilan ishlashda davom etsa, kalitlarni o'chiradi va yaratsa. Biroq, bu bizga etcd va Konsulda boshqa usullarni amalga oshirishni murakkablashtirmaslik imkonini berdi.

ZooKeeper-da o'rnatiladi

ZooKeeper-da daraxt strukturasi bilan ishlaydigan (yaratish, o'chirish, getChildren) va tugunlardagi ma'lumotlar bilan ishlaydigan (setData, getData) alohida usullar mavjud.Bundan tashqari, barcha usullar qat'iy shartlarga ega: agar tugun allaqachon mavjud bo'lsa, yaratish xatolikni qaytaradi. yaratilgan, o'chiring yoki setData - agar u allaqachon mavjud bo'lmasa. Bizga kalit mavjudligi haqida o'ylamasdan chaqirilishi mumkin bo'lgan to'siq usuli kerak edi.

Variantlardan biri, o'chirishda bo'lgani kabi, optimistik yondashuvni qo'llashdir. Tugun mavjudligini tekshiring. Agar mavjud bo'lsa, setData ga qo'ng'iroq qiling, aks holda yarating. Agar oxirgi usul xatoga yo'l qo'ygan bo'lsa, uni yana takrorlang. E'tiborga olish kerak bo'lgan birinchi narsa shundaki, mavjudlik testi ma'nosizdir. Siz darhol yaratishga qo'ng'iroq qilishingiz mumkin. Muvaffaqiyatli yakunlash tugun mavjud emasligini va u yaratilganligini anglatadi. Aks holda, yaratish tegishli xatoni qaytaradi, shundan so'ng siz setData-ga qo'ng'iroq qilishingiz kerak. Albatta, qo'ng'iroqlar orasidagi cho'qqi raqobatdosh qo'ng'iroq tomonidan o'chirilishi mumkin va setData ham xatolik qaytaradi. Bunday holda, siz hamma narsani qaytadan qilishingiz mumkin, ammo bunga arziydimi?

Agar ikkala usul ham xatoni qaytarsa, biz raqobatdosh o'chirish sodir bo'lganligini aniq bilamiz. Tasavvur qilaylik, bu o'chirish setni chaqirgandan keyin sodir bo'ldi. Keyin biz o'rnatishga harakat qilayotgan har qanday ma'no allaqachon o'chiriladi. Bu shuni anglatadiki, agar aslida hech narsa yozilmagan bo'lsa ham, to'plam muvaffaqiyatli bajarilgan deb taxmin qilishimiz mumkin.

Batafsil texnik tafsilotlar

Ushbu bo'limda biz taqsimlangan tizimlardan tanaffus qilamiz va kodlash haqida gapiramiz.
Mijozning asosiy talablaridan biri kross-platforma edi: xizmatlardan kamida bittasi Linux, MacOS va Windows tizimlarida qo‘llab-quvvatlanishi kerak. Dastlab biz faqat Linux uchun ishlab chiqdik va keyinroq boshqa tizimlarda sinovni boshladik. Bu ko'plab muammolarni keltirib chiqardi, ular bir muncha vaqtgacha qanday yondashish kerakligi noma'lum edi. Natijada, Linux va MacOS-da barcha uchta muvofiqlashtirish xizmatlari qo'llab-quvvatlanadi, Windows-da esa faqat Konsul KV qo'llab-quvvatlanadi.

Biz eng boshidan xizmatlarga kirish uchun tayyor kutubxonalardan foydalanishga harakat qildik. ZooKeeper misolida tanlov to'g'ri keldi ZooKeeper C++, natijada Windows-da kompilyatsiya qila olmadi. Biroq, bu ajablanarli emas: kutubxona faqat Linux uchun joylashgan. Konsul uchun yagona imkoniyat bor edi ppkonsul. Bunga qo'llab-quvvatlash qo'shilishi kerak edi sessiyalar и operatsiyalar. etcd uchun protokolning so'nggi versiyasini qo'llab-quvvatlaydigan to'liq huquqli kutubxona topilmadi, shuning uchun biz shunchaki yaratilgan grpc mijozi.

ZooKeeper C++ kutubxonasining asinxron interfeysidan ilhomlanib, biz asinxron interfeysni ham amalga oshirishga qaror qildik. ZooKeeper C++ buning uchun kelajak/promise primitivlaridan foydalanadi. STL-da, afsuski, ular juda kamtarona amalga oshiriladi. Masalan, yo'q keyin usul, u o'tgan funksiyani mavjud bo'lganda kelajak natijasiga qo'llaydi. Bizning holatda, natijani kutubxonamiz formatiga aylantirish uchun bunday usul zarur. Ushbu muammoni hal qilish uchun biz o'zimizning oddiy iplar pulimizni amalga oshirishimiz kerak edi, chunki mijozning iltimosiga binoan biz Boost kabi uchinchi tomonning og'ir kutubxonalaridan foydalana olmadik.

Bizning keyin amalga oshirishimiz shunday ishlaydi. Chaqirilganda qo'shimcha va'da/kelajak juftligi yaratiladi. Yangi kelajak qaytariladi va o'tgani mos keladigan funktsiya va navbatdagi qo'shimcha va'da bilan birga joylashtiriladi. Hovuzdagi ip navbatdan bir nechta fyucherslarni tanlaydi va wait_for yordamida ularni so'raydi. Natija mavjud bo'lganda, tegishli funktsiya chaqiriladi va uning qaytish qiymati va'daga o'tkaziladi.

Biz etcd va Konsulga so'rovlarni bajarish uchun bir xil mavzu pulidan foydalanganmiz. Bu shuni anglatadiki, asosiy kutubxonalarga bir nechta turli oqimlar orqali kirish mumkin. ppconsul ip xavfsiz emas, shuning uchun unga qo'ng'iroqlar qulflar bilan himoyalangan.
Siz grpc bilan bir nechta iplardan ishlashingiz mumkin, ammo nozikliklar mavjud. etcd da soatlar grpc oqimlari orqali amalga oshiriladi. Bu ma'lum turdagi xabarlar uchun ikki tomonlama kanallar. Kutubxona barcha soatlar uchun bitta ipni va kiruvchi xabarlarni qayta ishlaydigan bitta ipni yaratadi. Shunday qilib grpc oqimga parallel yozishni taqiqlaydi. Bu shuni anglatadiki, soatni ishga tushirish yoki o'chirishda keyingisini yuborishdan oldin oldingi so'rov yuborilishini kutishingiz kerak. Biz sinxronizatsiya uchun foydalanamiz shartli o'zgaruvchilar.

Xulosa

O'zingizga qarang: liboffkv.

Bizning jamoamiz: Raed Romanov, Ivan Glushenkov, Dmitriy Kamaldinov, Viktor Krapivenskiy, Vitaliy Ivanin.

Manba: www.habr.com

a Izoh qo'shish