Beş öğrenci ve üç dağıtılmış anahtar/değer deposu

Veya ZooKeeper, vb. ve Consul KV için nasıl bir istemci C++ kütüphanesi yazdığımızı

Dağıtılmış sistemler dünyasında bir dizi tipik görev vardır: kümenin bileşimi hakkında bilgi depolamak, düğümlerin konfigürasyonunu yönetmek, hatalı düğümleri tespit etmek, bir lider seçmek diğer. Bu sorunları çözmek için özel dağıtılmış sistemler oluşturulmuştur - koordinasyon hizmetleri. Şimdi bunlardan üçüyle ilgileneceğiz: ZooKeeper, vb. ve Consul. Consul'un tüm zengin işlevselliklerinden Consul KV'ye odaklanacağız.

Beş öğrenci ve üç dağıtılmış anahtar/değer deposu

Temelde tüm bu sistemler hataya dayanıklı, doğrusallaştırılabilir anahtar/değer depolarıdır. Her ne kadar veri modelleri daha sonra tartışacağımız önemli farklılıklara sahip olsa da aynı pratik sorunları çözüyorlar. Açıkçası, koordinasyon hizmetini kullanan her uygulama bunlardan birine bağlıdır ve bu da, farklı uygulamalar için aynı sorunları çözen birden fazla sistemin tek bir veri merkezinde desteklenmesi ihtiyacına yol açabilir.

Bu sorunu çözme fikri Avustralyalı bir danışmanlık şirketinden çıktı ve bunu uygulamak bize, yani küçük bir öğrenci ekibine düştü, ben de bundan bahsedeceğim.

ZooKeeper, vb. ve Consul KV ile çalışmak için ortak bir arayüz sağlayan bir kütüphane oluşturmayı başardık. Kütüphane C++ ile yazılmıştır, ancak onu diğer dillere taşıma planları da vardır.

Veri modelleri

Üç farklı sistem için ortak bir arayüz geliştirmek için bunların ortak yönlerini ve nasıl farklı olduklarını anlamanız gerekir. Hadi çözelim.

hayvan bakıcısı

Beş öğrenci ve üç dağıtılmış anahtar/değer deposu

Anahtarlar bir ağaç halinde düzenlenir ve düğümler olarak adlandırılır. Buna göre bir düğüm için onun çocuklarının bir listesini alabilirsiniz. Znode oluşturma (create) ve değer değiştirme (setData) işlemleri birbirinden ayrılmıştır: yalnızca mevcut anahtarlar okunabilir ve değiştirilebilir. Bir düğümün varlığını kontrol etme, bir değeri okuma ve çocuk alma işlemlerine saatler eklenebilir. İzleme, sunucudaki ilgili verilerin sürümü değiştiğinde tetiklenen tek seferlik bir tetikleyicidir. Geçici düğümler arızaları tespit etmek için kullanılır. Onları oluşturan müşterinin oturumuna bağlanırlar. Bir istemci bir oturumu kapattığında veya ZooKeeper'a varlığını bildirmeyi bıraktığında, bu düğümler otomatik olarak silinir. Basit işlemler desteklenir; en az biri için mümkün değilse hepsinin başarılı olduğu veya başarısız olduğu bir dizi işlem.

vb.

Beş öğrenci ve üç dağıtılmış anahtar/değer deposu

Bu sistemin geliştiricileri açıkça ZooKeeper'dan ilham aldılar ve bu nedenle her şeyi farklı yaptılar. Anahtarların hiyerarşisi yoktur ancak sözlükbilimsel olarak sıralı bir küme oluştururlar. Belirli bir aralığa ait tüm anahtarları alabilir veya silebilirsiniz. Bu yapı tuhaf görünebilir ama aslında oldukça etkileyicidir ve hiyerarşik bir bakış açısı bu yapı aracılığıyla kolayca taklit edilebilir.

etcd'nin standart bir karşılaştırma ve ayarlama işlemi yoktur, ancak daha iyi bir şeyi vardır: işlemler. Tabii ki her üç sistemde de mevcutlar, ancak vbd işlemleri özellikle iyidir. Üç bloktan oluşurlar: kontrol, başarı, başarısızlık. İlk blok bir dizi koşulu, ikinci ve üçüncü işlemleri içerir. İşlem atomik olarak gerçekleştirilir. Tüm koşullar doğruysa başarı bloğu yürütülür, aksi takdirde başarısızlık bloğu yürütülür. API 3.3'te başarı ve başarısızlık blokları iç içe geçmiş işlemler içerebilir. Yani, neredeyse keyfi yuvalama düzeyindeki koşullu yapıları atomik olarak yürütmek mümkündür. Hangi kontrollerin ve işlemlerin mevcut olduğu hakkında daha fazla bilgiyi şu adresten edinebilirsiniz: belgeleme.

Biraz daha karmaşık ve tekrar kullanılabilir olmalarına rağmen burada da saatler var. Yani, bir saati anahtar aralığa yükledikten sonra, siz saati iptal edene kadar yalnızca ilkini değil, bu aralıktaki tüm güncellemeleri alacaksınız. Etcd'de ZooKeeper istemci oturumlarının benzeri kiralamalardır.

Konsolos K.V.

Burada da katı bir hiyerarşik yapı yoktur, ancak Consul var olduğu görünümünü oluşturabilir: tüm anahtarları belirtilen önekle alabilir ve silebilirsiniz, yani anahtarın "alt ağacı" ile çalışabilirsiniz. Bu tür sorgulara özyinelemeli denir. Ek olarak, Konsolos yalnızca önekten sonra belirtilen karakteri içermeyen anahtarları seçebilir, bu da anında "çocuk" elde etmeye karşılık gelir. Ancak bunun tam olarak hiyerarşik bir yapının görünümü olduğunu hatırlamakta fayda var: Ebeveyni yoksa bir anahtar oluşturmak veya çocukları olan bir anahtarı silmek oldukça mümkündür, bu arada çocuklar sistemde saklanmaya devam edecektir.

Beş öğrenci ve üç dağıtılmış anahtar/değer deposu
Consul'un izleme yerine HTTP isteklerini engellemesi var. Özünde bunlar, diğer parametrelerle birlikte verilerin bilinen son versiyonunun belirtildiği veri okuma yöntemine yapılan sıradan çağrılardır. Sunucudaki karşılık gelen verilerin geçerli sürümü belirtilen sürümden büyükse, yanıt hemen döndürülür, aksi takdirde değer değiştiğinde. Anahtarlara istenildiği zaman eklenebilecek oturumlar da vardır. Oturumların silinmesinin ilişkili anahtarların silinmesine yol açtığı etcd ve ZooKeeper'dan farklı olarak, oturumun bunlarla bağlantısının kaldırıldığı bir modun bulunduğunu belirtmekte fayda var. Mevcut işlemler, şubesiz ama her türlü çekle.

Hepsini bir araya koy

ZooKeeper en titiz veri modeline sahiptir. Etcd'de bulunan ifade aralığı sorguları, ZooKeeper veya Consul'da etkili bir şekilde taklit edilemez. Tüm hizmetlerin en iyilerini birleştirmeye çalışırken, aşağıdaki önemli istisnalar dışında neredeyse ZooKeeper arayüzüne eşdeğer bir arayüz elde ettik:

  • dizi, kapsayıcı ve TTL düğümleri desteklenmiyor
  • ACL'ler desteklenmiyor
  • set yöntemi, mevcut değilse bir anahtar oluşturur (ZK setData'da bu durumda bir hata döndürür)
  • set ve cas yöntemleri ayrılmıştır (ZK'da bunlar aslında aynı şeydir)
  • silme yöntemi bir düğümü alt ağacıyla birlikte siler (ZK silmede, düğümün çocukları varsa bir hata döndürür)
  • Her anahtarın yalnızca bir sürümü vardır - değer sürümü (ZK cinsinden) üç tane var)

Sıralı düğümlerin reddedilmesi, etcd ve Consul'un bunlar için yerleşik desteğe sahip olmaması ve bunların, ortaya çıkan kütüphane arayüzünün üzerine kullanıcı tarafından kolayca uygulanabilmesinden kaynaklanmaktadır.

Bir köşeyi silerken ZooKeeper'a benzer bir davranış uygulamak, etcd ve Consul'daki her anahtar için ayrı bir alt sayacın tutulmasını gerektirir. Meta bilgileri saklamaktan kaçınmaya çalıştığımız için alt ağacın tamamının silinmesine karar verildi.

Uygulamanın incelikleri

Kütüphane arayüzünü farklı sistemlere uygulamanın bazı yönlerine daha yakından bakalım.

Etcd'deki hiyerarşi

Etcd'de hiyerarşik bir görünümü sürdürmenin en ilginç görevlerden biri olduğu ortaya çıktı. Aralık sorguları, belirtilen öneke sahip anahtarların listesini almayı kolaylaştırır. Örneğin, ile başlayan her şeye ihtiyacınız varsa "/foo", bir aralık istiyorsun ["/foo", "/fop"). Ancak bu, anahtarın alt ağacının tamamını döndürecektir; alt ağaç büyükse bu kabul edilebilir olmayabilir. İlk başta anahtar bir çeviri mekanizması kullanmayı planladık. zeetcd'de uygulandı. Anahtarın başına ağaçtaki düğümün derinliğine eşit bir bayt eklenmesini içerir. Sana bir örnek vereyim.

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

O zaman anahtarın tüm acil çocuklarını alın "/foo" bir aralık talep ederek mümkün ["u02/foo/", "u02/foo0"). Evet, ASCII'de "0" hemen ardından duruyor "/".

Ancak bu durumda tepe noktasının kaldırılması nasıl uygulanır? Türün tüm aralıklarını silmeniz gerektiği ortaya çıktı ["uXX/foo/", "uXX/foo0") XX için 01'den FF'ye. Ve sonra karşılaştık işlem numarası sınırı tek bir işlem içinde.

Sonuç olarak, hem anahtarı silmenin hem de çocuk listesinin alınmasının etkili bir şekilde uygulanmasını mümkün kılan basit bir anahtar dönüştürme sistemi icat edildi. Son belirteçten önce özel bir karakter eklemeniz yeterlidir. Örneğin:

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

Daha sonra anahtarı silmek "/very" silinmeye dönüşüyor "/u00very" ve aralık ["/very/", "/very0")ve tüm çocukları almak - serideki anahtarlar için talepte bulunmak ["/very/u00", "/very/u01").

ZooKeeper'da bir anahtarı kaldırma

Daha önce de belirttiğim gibi ZooKeeper'da çocukları varsa bir düğümü silemezsiniz. Anahtarı alt ağaçla birlikte silmek istiyoruz. Ne yapmalıyım? Bunu iyimserlikle yapıyoruz. İlk olarak, alt ağacı yinelemeli olarak geçerek her köşenin çocuklarını ayrı bir sorguyla elde ederiz. Daha sonra alt ağacın tüm düğümlerini doğru sırayla silmeye çalışan bir işlem oluştururuz. Elbette bir alt ağacın okunması ile silinmesi arasında değişiklikler meydana gelebilir. Bu durumda işlem başarısız olacaktır. Ayrıca alt ağaç okuma işlemi sırasında değişebilir. Bir sonraki düğümün alt öğelerine yönelik bir istek, örneğin bu düğüm zaten silinmişse bir hata döndürebilir. Her iki durumda da tüm süreci tekrarlıyoruz.

Bu yaklaşım, bir anahtarın çocukları varsa silinmesini çok etkisiz hale getirir ve uygulama alt ağaçla çalışmaya, anahtarları silmeye ve oluşturmaya devam ederse daha da etkisiz hale gelir. Ancak bu bize, etcd ve Consul'daki diğer yöntemlerin uygulanmasını zorlaştırmaktan kaçınmamızı sağladı.

ZooKeeper'da ayarlandı

ZooKeeper'da ağaç yapısıyla çalışan (create, delete, getChildren) ve düğümlerdeki verilerle (setData, getData) çalışan ayrı yöntemler vardır. Üstelik tüm yöntemlerin katı önkoşulları vardır: düğüm zaten varsa, create bir hata döndürecektir. oluşturulmuşsa, silin veya setData – halihazırda mevcut değilse. Bir anahtarın varlığını düşünmeden çağrılabilecek bir dizi yönteme ihtiyacımız vardı.

Seçeneklerden biri, silme konusunda olduğu gibi iyimser bir yaklaşım benimsemektir. Bir düğümün olup olmadığını kontrol edin. Varsa setData'yı çağırın, yoksa oluşturun. Son yöntem bir hata döndürdüyse, her şeyi tekrar tekrarlayın. Dikkat edilmesi gereken ilk şey, varlık testinin anlamsız olduğudur. Create'i hemen arayabilirsiniz. Başarılı tamamlama, düğümün mevcut olmadığı ve oluşturulduğu anlamına gelecektir. Aksi takdirde, create uygun hatayı döndürecektir ve ardından setData'yı çağırmanız gerekir. Elbette, çağrılar arasında, bir köşe noktası rakip bir çağrı tarafından silinebilir ve setData da bir hata döndürebilir. Bu durumda her şeyi yeniden yapabilirsiniz ama buna değer mi?

Her iki yöntem de hata verirse, rakip bir silme işleminin gerçekleştiğinden eminiz. Bu silme işleminin set çağrıldıktan sonra gerçekleştiğini düşünelim. O zaman kurmaya çalıştığımız anlam her ne ise, çoktan silinmiştir. Bu, aslında hiçbir şey yazılmamış olsa bile setin başarıyla yürütüldüğünü varsayabileceğimiz anlamına gelir.

Daha fazla teknik detay

Bu bölümde dağıtık sistemlere biraz ara verip kodlamadan bahsedeceğiz.
Müşterinin temel gereksinimlerinden biri çapraz platformdu: hizmetlerden en az birinin Linux, MacOS ve Windows'ta desteklenmesi gerekiyor. Başlangıçta yalnızca Linux için geliştirdik ve daha sonra diğer sistemler üzerinde de test etmeye başladık. Bu, bir süredir nasıl yaklaşılacağı tamamen belirsiz olan birçok soruna neden oldu. Sonuç olarak, üç koordinasyon hizmetinin tümü artık Linux ve MacOS'ta desteklenirken, Windows'ta yalnızca Consul KV desteklenmektedir.

En başından beri hizmetlere erişim için hazır kütüphaneleri kullanmaya çalıştık. ZooKeeper'ın durumunda seçim düştü ZooKeeper C++sonuçta Windows'ta derlenemedi. Ancak bu şaşırtıcı değil: kütüphane yalnızca linux olarak konumlandırılmıştır. Konsolos için tek seçenek şuydu: ppconsul. Buna desteğin eklenmesi gerekiyordu oturumları и işlemler. Etcd için protokolün en son sürümünü destekleyen tam teşekküllü bir kütüphane bulunamadı, bu yüzden sadece oluşturulan grpc istemcisi.

ZooKeeper C++ kütüphanesinin eşzamansız arayüzünden esinlenerek, aynı zamanda eşzamansız bir arayüz uygulamaya karar verdik. ZooKeeper C++ bunun için gelecek/söz ilkellerini kullanır. STL'de maalesef çok mütevazı bir şekilde uygulanıyorlar. Örneğin hayır sonra yöntemGeçilen işlevi, kullanılabilir olduğunda geleceğin sonucuna uygulayan. Bizim durumumuzda sonucu kütüphanemizin formatına dönüştürmek için böyle bir yöntem gereklidir. Bu sorunu aşmak için kendi basit iş parçacığı havuzumuzu uygulamak zorunda kaldık çünkü müşterinin isteği üzerine Boost gibi ağır üçüncü taraf kütüphaneleri kullanamadık.

Daha sonra uygulamamız bu şekilde çalışıyor. Çağrıldığında ek bir söz/gelecek çifti oluşturulur. Yeni gelecek döndürülür ve geçmiş olan, karşılık gelen işlev ve ek bir sözle birlikte kuyruğa yerleştirilir. Havuzdaki bir iş parçacığı kuyruktan birkaç vadeli işlem seçer ve bunları wait_for kullanarak yoklar. Bir sonuç elde edildiğinde ilgili fonksiyon çağrılır ve dönüş değeri söze iletilir.

Etcd ve Consul'a sorgu yürütmek için aynı iş parçacığı havuzunu kullandık. Bu, temel kitaplıklara birden çok farklı iş parçacığı tarafından erişilebileceği anlamına gelir. ppconsul iş parçacığı için güvenli değildir, dolayısıyla ona yapılan çağrılar kilitlerle korunmaktadır.
Birden fazla iş parçacığından grpc ile çalışabilirsiniz, ancak incelikler vardır. Etcd'de saatler grpc akışları aracılığıyla uygulanır. Bunlar belirli bir türdeki mesajlar için çift yönlü kanallardır. Kitaplık, tüm saatler için tek bir iş parçacığı ve gelen mesajları işleyen tek bir iş parçacığı oluşturur. Yani grpc akışa paralel yazmaları yasaklar. Bu, bir saati başlatırken veya silerken, bir sonraki isteği göndermeden önce önceki isteğin gönderilmesinin tamamlanmasını beklemeniz gerektiği anlamına gelir. Senkronizasyon için kullanıyoruz koşullu değişkenler.

sonuç

Kendiniz için bakınız: liboffkv.

Bizim takım: Raed Romanov, Ivan Gluşenkov, Dmitri Kamaldinov, Viktor Krapivensky, Vitaly Ivanin.

Kaynak: habr.com

Yorum ekle