Lima pelajar dan tiga stor nilai kunci yang diedarkan

Atau cara kami menulis perpustakaan C++ pelanggan untuk ZooKeeper, etcd dan Consul KV

Dalam dunia sistem teragih, terdapat beberapa tugas biasa: menyimpan maklumat tentang komposisi kluster, mengurus konfigurasi nod, mengesan nod yang rosak, memilih pemimpin dan lain-lain. Untuk menyelesaikan masalah ini, sistem teragih khas telah dicipta - perkhidmatan penyelarasan. Sekarang kita akan berminat dengan tiga daripadanya: Penjaga Zoo, dll dan Konsul. Daripada semua fungsi Konsul yang kaya, kami akan menumpukan pada Consul KV.

Lima pelajar dan tiga stor nilai kunci yang diedarkan

Pada dasarnya, semua sistem ini adalah storan nilai kunci yang boleh digariskan dan tahan terhadap kesalahan. Walaupun model data mereka mempunyai perbezaan yang ketara, yang akan kita bincangkan kemudian, mereka menyelesaikan masalah praktikal yang sama. Jelas sekali, setiap aplikasi yang menggunakan perkhidmatan penyelarasan terikat pada salah satu daripadanya, yang mungkin membawa kepada keperluan untuk menyokong beberapa sistem dalam satu pusat data yang menyelesaikan masalah yang sama untuk aplikasi yang berbeza.

Idea untuk menyelesaikan masalah ini berasal dari sebuah agensi perunding Australia, dan ia jatuh kepada kami, sekumpulan kecil pelajar, untuk melaksanakannya, itulah yang saya akan bincangkan.

Kami berjaya mencipta perpustakaan yang menyediakan antara muka biasa untuk bekerja dengan ZooKeeper, etcd dan Consul KV. Perpustakaan ini ditulis dalam C++, tetapi terdapat rancangan untuk memindahkannya ke bahasa lain.

Model Data

Untuk membangunkan antara muka yang sama untuk tiga sistem yang berbeza, anda perlu memahami persamaan mereka dan bagaimana ia berbeza. Mari kita fikirkan.

Penjaga zoo

Lima pelajar dan tiga stor nilai kunci yang diedarkan

Kekunci disusun menjadi pokok dan dipanggil nod. Oleh itu, untuk nod anda boleh mendapatkan senarai anak-anaknya. Operasi mencipta znod (buat) dan menukar nilai (setData) dipisahkan: hanya kekunci sedia ada boleh dibaca dan ditukar. Jam tangan boleh dilampirkan pada operasi menyemak kewujudan nod, membaca nilai, dan mendapatkan kanak-kanak. Tonton ialah pencetus sekali sahaja yang menyala apabila versi data yang sepadan pada pelayan berubah. Nod ephemeral digunakan untuk mengesan kegagalan. Mereka terikat dengan sesi pelanggan yang mencipta mereka. Apabila pelanggan menutup sesi atau berhenti memberitahu ZooKeeper tentang kewujudannya, nod ini dipadamkan secara automatik. Urus niaga mudah disokong - satu set operasi yang sama ada semuanya berjaya atau gagal jika ini tidak mungkin untuk sekurang-kurangnya satu daripadanya.

dll

Lima pelajar dan tiga stor nilai kunci yang diedarkan

Pembangun sistem ini jelas diilhamkan oleh ZooKeeper, dan oleh itu melakukan semuanya secara berbeza. Tiada hierarki kunci, tetapi ia membentuk set tersusun secara leksikografi. Anda boleh mendapatkan atau memadam semua kunci kepunyaan julat tertentu. Struktur ini mungkin kelihatan pelik, tetapi ia sebenarnya sangat ekspresif, dan pandangan hierarki boleh dicontohi dengan mudah melaluinya.

etcd tidak mempunyai operasi bandingkan dan tetapkan standard, tetapi ia mempunyai sesuatu yang lebih baik: transaksi. Sudah tentu, mereka wujud dalam ketiga-tiga sistem, tetapi urus niaga etcd adalah sangat baik. Mereka terdiri daripada tiga blok: semak, kejayaan, kegagalan. Blok pertama mengandungi satu set syarat, kedua dan ketiga - operasi. Urus niaga dilaksanakan secara atom. Jika semua syarat adalah benar, maka blok kejayaan dilaksanakan, jika tidak blok kegagalan dilaksanakan. Dalam API 3.3, blok kejayaan dan kegagalan boleh mengandungi transaksi bersarang. Iaitu, adalah mungkin untuk melaksanakan binaan bersyarat secara atom pada tahap sarang yang hampir sewenang-wenangnya. Anda boleh mengetahui lebih lanjut tentang semakan dan operasi yang wujud dokumentasi.

Jam tangan juga wujud di sini, walaupun ia sedikit lebih rumit dan boleh digunakan semula. Iaitu, selepas memasang jam tangan pada julat utama, anda akan menerima semua kemas kini dalam julat ini sehingga anda membatalkan jam tangan, dan bukan hanya yang pertama. Dalam etcd, analog sesi pelanggan ZooKeeper adalah pajakan.

Konsul K.V.

Juga tiada struktur hierarki yang ketat di sini, tetapi Konsul boleh mencipta rupa yang wujud: anda boleh mendapatkan dan memadam semua kunci dengan awalan yang ditentukan, iaitu, bekerja dengan "subpokok" kunci. Pertanyaan sedemikian dipanggil rekursif. Di samping itu, Konsul boleh memilih hanya kekunci yang tidak mengandungi aksara yang ditentukan selepas awalan, yang sepadan dengan mendapatkan "kanak-kanak" segera. Tetapi perlu diingat bahawa ini adalah tepat rupa struktur hierarki: adalah agak mungkin untuk mencipta kunci jika induknya tidak wujud atau memadamkan kunci yang mempunyai anak, sementara anak-anak akan terus disimpan dalam sistem.

Lima pelajar dan tiga stor nilai kunci yang diedarkan
Daripada jam tangan, Consul telah menyekat permintaan HTTP. Pada dasarnya, ini adalah panggilan biasa kepada kaedah membaca data, yang mana, bersama-sama dengan parameter lain, versi terakhir data yang diketahui ditunjukkan. Jika versi semasa data yang sepadan pada pelayan lebih besar daripada yang ditentukan, respons dikembalikan serta-merta, jika tidak - apabila nilai berubah. Terdapat juga sesi yang boleh dilampirkan pada kekunci pada bila-bila masa. Perlu diingat bahawa tidak seperti etcd dan ZooKeeper, di mana pemadaman sesi membawa kepada pemadaman kunci yang berkaitan, terdapat mod di mana sesi itu hanya dinyahpautkan daripadanya. Tersedia urus niaga, tanpa cawangan, tetapi dengan semua jenis cek.

Menyatukan semuanya

ZooKeeper mempunyai model data yang paling ketat. Pertanyaan julat ekspresif yang tersedia dalam etcd tidak boleh dicontohi dengan berkesan sama ada dalam ZooKeeper atau Consul. Cuba untuk menggabungkan yang terbaik daripada semua perkhidmatan, kami mendapat antara muka yang hampir setara dengan antara muka ZooKeeper dengan pengecualian penting berikut:

  • urutan, bekas dan nod TTL tidak disokong
  • ACL tidak disokong
  • kaedah set mencipta kunci jika ia tidak wujud (dalam ZK setData mengembalikan ralat dalam kes ini)
  • kaedah set dan cas dipisahkan (dalam ZK mereka pada dasarnya adalah perkara yang sama)
  • kaedah padam memadamkan nod bersama-sama dengan subpokoknya (dalam ZK delete mengembalikan ralat jika nod itu mempunyai anak)
  • Untuk setiap kunci hanya terdapat satu versi - versi nilai (dalam ZK ada tiga daripadanya)

Penolakan nod berjujukan adalah disebabkan oleh fakta bahawa etcd dan Consul tidak mempunyai sokongan terbina dalam untuknya, dan ia boleh dilaksanakan dengan mudah oleh pengguna di atas antara muka perpustakaan yang terhasil.

Melaksanakan tingkah laku yang serupa dengan ZooKeeper apabila memadamkan bucu memerlukan pengekalan pembilang anak yang berasingan untuk setiap kunci dalam etcd dan Konsul. Memandangkan kami cuba mengelak daripada menyimpan maklumat meta, kami telah memutuskan untuk memadamkan keseluruhan subpokok.

Kehalusan pelaksanaan

Mari kita lihat dengan lebih dekat beberapa aspek pelaksanaan antara muka perpustakaan dalam sistem yang berbeza.

Hierarki dalam dll

Mengekalkan pandangan hierarki dalam etcd ternyata menjadi salah satu tugas yang paling menarik. Pertanyaan julat memudahkan untuk mendapatkan semula senarai kunci dengan awalan yang ditentukan. Sebagai contoh, jika anda memerlukan semua yang bermula dengan "/foo", anda meminta julat ["/foo", "/fop"). Tetapi ini akan mengembalikan keseluruhan subpokok kunci, yang mungkin tidak boleh diterima jika subpokok itu besar. Pada mulanya kami merancang untuk menggunakan mekanisme terjemahan utama, dilaksanakan dalam zetcd. Ia melibatkan penambahan satu bait pada permulaan kunci, sama dengan kedalaman nod dalam pokok. Biar saya berikan satu contoh.

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

Kemudian dapatkan semua anak kunci yang terdekat "/foo" mungkin dengan meminta julat ["u02/foo/", "u02/foo0"). Ya, dalam ASCII "0" berdiri sejurus selepas itu "/".

Tetapi bagaimana untuk melaksanakan penyingkiran bucu dalam kes ini? Ternyata anda perlu memadam semua julat jenis ["uXX/foo/", "uXX/foo0") untuk XX dari 01 hingga FF. Dan kemudian kami terserempak had nombor operasi dalam satu transaksi.

Akibatnya, sistem penukaran kunci yang mudah telah dicipta, yang memungkinkan untuk melaksanakan kedua-dua pemadaman kunci dan mendapatkan senarai kanak-kanak dengan berkesan. Ia cukup untuk menambah watak istimewa sebelum token terakhir. Sebagai contoh:

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

Kemudian memadamkan kunci "/very" bertukar menjadi penghapusan "/u00very" dan julat ["/very/", "/very0"), dan mendapatkan semua kanak-kanak - dalam permintaan untuk kunci daripada julat ["/very/u00", "/very/u01").

Mengalih keluar kunci dalam ZooKeeper

Seperti yang telah saya nyatakan, dalam ZooKeeper anda tidak boleh memadamkan nod jika ia mempunyai anak. Kami mahu memadamkan kunci bersama-sama dengan subpokok. Apa patut saya buat? Kami melakukan ini dengan optimis. Mula-mula, kami melintasi subpokok secara rekursif, mendapatkan anak bagi setiap bucu dengan pertanyaan berasingan. Kemudian kami membina transaksi yang cuba memadam semua nod subpokok dalam susunan yang betul. Sudah tentu, perubahan boleh berlaku antara membaca subpokok dan memadamkannya. Dalam kes ini, transaksi akan gagal. Selain itu, subpokok mungkin berubah semasa proses membaca. Permintaan untuk anak-anak nod seterusnya mungkin mengembalikan ralat jika, sebagai contoh, nod ini telah dipadamkan. Dalam kedua-dua kes, kami mengulangi keseluruhan proses sekali lagi.

Pendekatan ini menjadikan pemadaman kunci sangat tidak berkesan jika ia mempunyai anak, dan lebih-lebih lagi jika aplikasi terus berfungsi dengan subpokok, memadam dan mencipta kunci. Walau bagaimanapun, ini membolehkan kami mengelak daripada merumitkan pelaksanaan kaedah lain dalam etcd dan Consul.

ditetapkan dalam ZooKeeper

Dalam ZooKeeper terdapat kaedah berasingan yang berfungsi dengan struktur pokok (buat, padam, getChildren) dan yang berfungsi dengan data dalam nod (setData, getData). Selain itu, semua kaedah mempunyai prasyarat yang ketat: create akan mengembalikan ralat jika nod telah pun telah dibuat, padam atau setData – jika ia belum wujud. Kami memerlukan kaedah set yang boleh dipanggil tanpa memikirkan kehadiran kunci.

Satu pilihan ialah mengambil pendekatan optimistik, seperti pemadaman. Semak sama ada nod wujud. Jika wujud, panggil setData, jika tidak buat. Jika kaedah terakhir mengembalikan ralat, ulangi sekali lagi. Perkara pertama yang perlu diperhatikan ialah ujian kewujudan adalah sia-sia. Anda boleh segera memanggil buat. Penyiapan yang berjaya bermakna nod itu tidak wujud dan ia telah dicipta. Jika tidak, cipta akan mengembalikan ralat yang sesuai, selepas itu anda perlu memanggil setData. Sudah tentu, antara panggilan, puncak boleh dipadamkan oleh panggilan bersaing, dan setData juga akan mengembalikan ralat. Dalam kes ini, anda boleh melakukannya sekali lagi, tetapi adakah ia berbaloi?

Jika kedua-dua kaedah mengembalikan ralat, maka kami tahu pasti bahawa pemadaman bersaing telah berlaku. Mari bayangkan bahawa pemadaman ini berlaku selepas panggilan ditetapkan. Kemudian apa sahaja makna yang kita cuba wujudkan sudah terpadam. Ini bermakna kita boleh menganggap bahawa set telah dilaksanakan dengan jayanya, walaupun sebenarnya tiada apa yang ditulis.

Butiran lanjut teknikal

Dalam bahagian ini kita akan berehat daripada sistem yang diedarkan dan bercakap tentang pengekodan.
Salah satu keperluan utama pelanggan ialah merentas platform: sekurang-kurangnya satu daripada perkhidmatan mesti disokong pada Linux, MacOS dan Windows. Pada mulanya, kami membangunkan hanya untuk Linux, dan mula menguji pada sistem lain kemudian. Ini menyebabkan banyak masalah, yang untuk beberapa waktu tidak jelas bagaimana untuk mendekati. Akibatnya, ketiga-tiga perkhidmatan penyelarasan kini disokong pada Linux dan MacOS, manakala hanya Consul KV disokong pada Windows.

Sejak awal lagi, kami cuba menggunakan perpustakaan sedia untuk mengakses perkhidmatan. Dalam kes ZooKeeper, pilihan jatuh pada ZooKeeper C++, yang akhirnya gagal untuk disusun pada Windows. Ini, bagaimanapun, tidak menghairankan: perpustakaan diletakkan sebagai linux sahaja. Bagi Konsul satu-satunya pilihan ialah ppkonsul. Sokongan terpaksa ditambah kepadanya sesi и urus niaga. Untuk etcd, perpustakaan lengkap yang menyokong versi terkini protokol tidak ditemui, jadi kami hanya pelanggan grpc yang dihasilkan.

Diilhamkan oleh antara muka tak segerak pustaka ZooKeeper C++, kami memutuskan untuk turut melaksanakan antara muka tak segerak. ZooKeeper C++ menggunakan primitif masa depan/janji untuk ini. Dalam STL, malangnya, ia dilaksanakan dengan sangat sederhana. Sebagai contoh, tidak kemudian kaedah, yang menggunakan fungsi yang diluluskan pada hasil masa hadapan apabila ia tersedia. Dalam kes kami, kaedah sedemikian diperlukan untuk menukar hasil ke dalam format perpustakaan kami. Untuk mengatasi masalah ini, kami terpaksa melaksanakan kumpulan benang mudah kami sendiri, kerana atas permintaan pelanggan kami tidak boleh menggunakan perpustakaan pihak ketiga yang berat seperti Boost.

Pelaksanaan kami kemudian berfungsi seperti ini. Apabila dipanggil, janji tambahan/pasangan masa hadapan dibuat. Masa depan baharu dikembalikan, dan yang diluluskan diletakkan bersama-sama dengan fungsi yang sepadan dan janji tambahan dalam baris gilir. Urutan daripada kumpulan memilih beberapa hadapan daripada baris gilir dan mengundi mereka menggunakan wait_for. Apabila hasil menjadi tersedia, fungsi yang sepadan dipanggil dan nilai pulangannya diserahkan kepada janji.

Kami menggunakan kumpulan benang yang sama untuk melaksanakan pertanyaan kepada etcd dan Konsul. Ini bermakna perpustakaan asas boleh diakses oleh berbilang benang yang berbeza. ppconsul tidak selamat untuk thread, jadi panggilan kepadanya dilindungi oleh kunci.
Anda boleh bekerja dengan grpc daripada berbilang benang, tetapi terdapat kehalusan. Dalam jam tangan etcd dilaksanakan melalui aliran grpc. Ini adalah saluran dwiarah untuk mesej jenis tertentu. Pustaka mencipta satu utas untuk semua jam tangan dan satu utas yang memproses mesej masuk. Jadi grpc melarang penulisan selari ke strim. Ini bermakna apabila memulakan atau memadamkan jam tangan, anda mesti menunggu sehingga permintaan sebelumnya telah selesai menghantar sebelum menghantar yang seterusnya. Kami gunakan untuk penyegerakan pembolehubah bersyarat.

Jumlah

Lihat sendiri: liboffkv.

Pasukan kami: Raed Romanov, Ivan Glushenkov, Dmitry Kamaldinov, Victor Krapivensky, Vitaly Ivanin.

Sumber: www.habr.com

Tambah komen