Yalnızca işleme değil: Kafka Streams'ten nasıl dağıtılmış bir veritabanı oluşturduk ve bunun sonuçları

Ey Habr!

Hakkındaki kitabı takip etmenizi hatırlatırız. Kafka kütüphane hakkında aynı derecede ilginç bir çalışma yayınladık Kafka Akışları API'sı.

Yalnızca işleme değil: Kafka Streams'ten nasıl dağıtılmış bir veritabanı oluşturduk ve bunun sonuçları

Şimdilik topluluk bu güçlü aracın sınırlarını yeni öğreniyor. Yakın zamanda çevirisini size tanıtmak istediğimiz bir makale yayınlandı. Yazar, kendi deneyiminden yola çıkarak Kafka Akışlarını dağıtılmış bir veri deposuna nasıl dönüştüreceğini anlatıyor. Okumanın tadını çıkar!

Apache kütüphanesi Kafka Akışları Apache Kafka'nın yanı sıra dağıtılmış akış işleme için dünya çapında işletmelerde kullanılmaktadır. Bu çerçevenin yeterince takdir edilmeyen yönlerinden biri, iş parçacığı işlemeye dayalı olarak üretilen yerel durumu saklamanıza izin vermesidir.

Bu yazımda firmamızın bulut uygulama güvenliğine yönelik bir ürün geliştirirken bu fırsatı nasıl karlı bir şekilde kullanmayı başardığını anlatacağım. Kafka Streams'i kullanarak, her biri sistemdeki nesnelerin durumu hakkında hataya dayanıklı ve yüksek düzeyde kullanılabilir güvenilir bilgi kaynağı olarak hizmet veren paylaşılan durum mikro hizmetleri oluşturduk. Bizim için bu, hem güvenilirlik hem de destek kolaylığı açısından ileriye doğru atılmış bir adımdır.

Nesnelerinizin resmi durumunu desteklemek için tek bir merkezi veritabanı kullanmanıza olanak tanıyan alternatif bir yaklaşımla ilgileniyorsanız, okuyun, ilginç olacaktır...

Neden paylaşılan durumla çalışma şeklimizi değiştirme zamanının geldiğini düşündük?

Temsilci raporlarına dayanarak çeşitli nesnelerin durumunu korumamız gerekiyordu (örneğin: site saldırı altında mıydı)? Kafka Streams'e geçmeden önce durum yönetimi için genellikle tek bir merkezi veritabanına (+ hizmet API'sine) güveniyorduk. Bu yaklaşımın dezavantajları vardır: randevu yoğun durumlar tutarlılığı ve senkronizasyonu sürdürmek gerçek bir zorluk haline gelir. Veritabanı bir darboğaz haline gelebilir veya yarış kondisyonu ve öngörülemezlikten muzdariptir.

Yalnızca işleme değil: Kafka Streams'ten nasıl dağıtılmış bir veritabanı oluşturduk ve bunun sonuçları

Şekil 1: Geçiş öncesinde görülen tipik bir bölünmüş durum senaryosu
Kafka ve Kafka Akışları: aracılar görüşlerini API aracılığıyla iletir, güncellenmiş durum merkezi bir veritabanı aracılığıyla hesaplanır

Paylaşılan durum mikro hizmetleri oluşturmayı kolaylaştıran Kafka Streams ile tanışın

Yaklaşık bir yıl önce, bu sorunları çözmek için ortak durum senaryolarımızı derinlemesine incelemeye karar verdik. Hemen Kafka Akışlarını denemeye karar verdik; ne kadar ölçeklenebilir, yüksek düzeyde kullanılabilir ve hataya dayanıklı olduğunu ve akış işlevselliğinin ne kadar zengin olduğunu (durum bilgisi olanlar dahil dönüşümler) biliyoruz. Tam da ihtiyacımız olan şey, Kafka'daki mesajlaşma sisteminin ne kadar olgun ve güvenilir hale geldiğinden bahsetmiyorum bile.

Oluşturduğumuz durum bilgisi olan mikro hizmetlerin her biri, oldukça basit bir topolojiye sahip bir Kafka Streams örneğinin üzerine inşa edildi. 1) bir kaynaktan 2) kalıcı anahtar/değer deposuna sahip bir işlemciden 3) bir havuzdan oluşuyordu:

Yalnızca işleme değil: Kafka Streams'ten nasıl dağıtılmış bir veritabanı oluşturduk ve bunun sonuçları

Şekil 2: Durum bilgisi olan mikro hizmetler için akış örneklerimizin varsayılan topolojisi. Burada planlama meta verilerini içeren bir havuzun da bulunduğunu unutmayın.

Bu yeni yaklaşımda, aracılar kaynak konuya beslenen mesajları oluşturur ve tüketiciler (örneğin bir posta bildirim hizmeti) havuz (çıkış konusu) aracılığıyla hesaplanan paylaşılan durumu alır.

Yalnızca işleme değil: Kafka Streams'ten nasıl dağıtılmış bir veritabanı oluşturduk ve bunun sonuçları

Şekil 3: Paylaşılan mikro hizmetlere sahip bir senaryo için yeni örnek görev akışı: 1) aracı, Kafka kaynak konusuna ulaşan bir mesaj oluşturur; 2) paylaşılan duruma sahip bir mikro hizmet (Kafka Akışlarını kullanarak) bunu işler ve hesaplanan durumu son Kafka konusuna yazar; bundan sonra 3) tüketiciler yeni durumu kabul eder

Hey, bu yerleşik anahtar/değer deposu aslında çok kullanışlıdır!

Yukarıda belirtildiği gibi, paylaşılan durum topolojimiz bir anahtar/değer deposu içerir. Bunu kullanmak için birkaç seçenek bulduk ve bunlardan ikisi aşağıda açıklanmıştır.

Seçenek #1: Hesaplamalar için anahtar/değer deposu kullanın

İlk anahtar/değer depomuz hesaplamalar için ihtiyacımız olan yardımcı verileri içeriyordu. Örneğin bazı durumlarda ortak durum "çoğunluk oyu" ilkesine göre belirleniyordu. Depo, bazı nesnelerin durumuna ilişkin en son aracı raporlarının tümünü tutabilir. Daha sonra, bir temsilciden veya diğerinden yeni bir rapor aldığımızda, bunu kaydedebilir, depodaki aynı nesnenin durumu hakkında diğer tüm aracıların raporlarını alabilir ve hesaplamayı tekrarlayabilirdik.
Aşağıdaki Şekil 4, yeni mesajın işlenebilmesi için anahtar/değer deposunu işlemcinin işleme yöntemine nasıl maruz bıraktığımızı göstermektedir.

Yalnızca işleme değil: Kafka Streams'ten nasıl dağıtılmış bir veritabanı oluşturduk ve bunun sonuçları

İllüstrasyon 4: İşlemcinin işleme yöntemi için anahtar/değer deposuna erişim açıyoruz (bundan sonra paylaşımlı durumla çalışan her betiğin yöntemi uygulaması gerekir) doProcess)

Seçenek #2: Kafka Akışlarının Üstünde CRUD API Oluşturma

Temel görev akışımızı oluşturduktan sonra, paylaşılan durum mikro servislerimiz için RESTful CRUD API yazmaya başladık. Nesnelerin bir kısmının veya tamamının durumunu alabilmenin yanı sıra bir nesnenin durumunu ayarlayabilmeyi veya kaldırabilmeyi (arka uç desteği için kullanışlıdır) istedik.

Tüm Get State API'lerini desteklemek için, işleme sırasında durumu yeniden hesaplamamız gerektiğinde, bunu yerleşik bir anahtar/değer deposunda uzun süre sakladık. Bu durumda, aşağıdaki listede gösterildiği gibi, Kafka Streams'in tek bir örneğini kullanarak böyle bir API'yi uygulamak oldukça basit hale gelir:

Yalnızca işleme değil: Kafka Streams'ten nasıl dağıtılmış bir veritabanı oluşturduk ve bunun sonuçları

Şekil 5: Bir nesnenin önceden hesaplanmış durumunu elde etmek için yerleşik anahtar/değer deposunu kullanma

Bir nesnenin durumunu API aracılığıyla güncellemenin uygulanması da kolaydır. Temel olarak yapmanız gereken tek şey bir Kafka yapımcısı oluşturmak ve onu yeni durumu içeren bir kayıt oluşturmak için kullanmaktır. Bu, API aracılığıyla oluşturulan tüm mesajların diğer üreticilerden (örn. aracılar) alınanlarla aynı şekilde işlenmesini sağlar.

Yalnızca işleme değil: Kafka Streams'ten nasıl dağıtılmış bir veritabanı oluşturduk ve bunun sonuçları

Şekil 6: Kafka yapımcısını kullanarak bir nesnenin durumunu ayarlayabilirsiniz

Küçük komplikasyon: Kafka'nın birçok bölümü var

Daha sonra, senaryo başına bir paylaşılan durum mikro hizmetleri kümesi sağlayarak işlem yükünü dağıtmak ve kullanılabilirliği artırmak istedik. Kurulum çok kolay oldu: Tüm örnekleri aynı uygulama kimliği (ve aynı önyükleme sunucuları) altında çalışacak şekilde yapılandırdığımızda, geri kalan hemen hemen her şey otomatik olarak yapıldı. Ayrıca her kaynak konusunun birkaç bölümden oluşacağını da belirttik, böylece her bir örneğe bu bölümlerin bir alt kümesi atanabilir.

Ayrıca, örneğin bir arızadan sonra kurtarma durumunda bu kopyayı başka bir örneğe aktarmak için durum deposunun yedek bir kopyasını oluşturmanın yaygın bir uygulama olduğunu da belirteceğim. Kafka Akışlarındaki her durum deposu için, bir değişiklik günlüğüyle (yerel güncellemeleri izleyen) çoğaltılmış bir konu oluşturulur. Böylece Kafka devlet deposunu sürekli yedekliyor. Bu nedenle, bir veya başka bir Kafka Akışı örneğinin arızalanması durumunda, durum deposu, ilgili bölümlerin gideceği başka bir örneğe hızlı bir şekilde geri yüklenebilir. Testlerimiz, mağazada milyonlarca kayıt olsa bile bunun birkaç saniye içinde yapıldığını gösterdi.

Paylaşılan duruma sahip tek bir mikro hizmetten bir mikro hizmet kümesine geçiş, Get State API'yi uygulamak daha az önemsiz hale gelir. Yeni durumda, her mikro hizmetin durum deposu genel resmin yalnızca bir kısmını içerir (anahtarları belirli bir bölümle eşlenen nesneler). İhtiyacımız olan nesnenin durumunu hangi örneğin içerdiğini belirlememiz gerekiyordu ve bunu aşağıda gösterildiği gibi iş parçacığı meta verilerine dayanarak yaptık:

Yalnızca işleme değil: Kafka Streams'ten nasıl dağıtılmış bir veritabanı oluşturduk ve bunun sonuçları

Şekil 7: Akış meta verilerini kullanarak, istenilen nesnenin durumunun hangi örnekten sorgulanacağını belirliyoruz; GET ALL API'sinde de benzer bir yaklaşım kullanıldı

Ana sonuçlar

Kafka Streams'teki durum depoları fiili olarak dağıtılmış bir veritabanı olarak hizmet verebilir,

  • Kafka'da sürekli kopyalanıyor
  • Böyle bir sistemin üzerine kolayca bir CRUD API oluşturulabilir
  • Birden fazla bölümü yönetmek biraz daha karmaşıktır
  • Yardımcı verileri depolamak için akış topolojisine bir veya daha fazla durum deposu eklemek de mümkündür. Bu seçenek aşağıdakiler için kullanılabilir:
  • Akış işleme sırasında hesaplamalar için gereken verilerin uzun süreli depolanması
  • Akış örneğinin bir sonraki hazırlığında faydalı olabilecek verilerin uzun süreli depolanması
  • daha fazla...

Bunlar ve diğer avantajlar, Kafka Akışlarını bizimki gibi dağıtılmış bir sistemde küresel durumu korumaya çok uygun hale getiriyor. Kafka Streams'in üretimde çok güvenilir olduğu kanıtlandı (dağıtıldığından beri neredeyse hiç mesaj kaybı yaşamadık) ve yeteneklerinin burada bitmeyeceğinden eminiz!

Kaynak: habr.com

Yorum ekle