Redis’de Data Consistency’yi .Net Core Ortamında Mikroservisler İle Sağlama

Selamlar,

Bu makalede, anlık data değişiminin çok olmadığı yapılarda, farklı sunucularda bulunan Redis üzerindeki datanın, tüm sunucularda eşitlenmesi yani aynı olması konusu, “redis master-slave” ilişkisi kurulmadan microservis mantığında  incelenmiştir. Aslında hikaye şöyle başlıyor, master yazma, slaveler ise ‘N’ sayıdaki readonly okuma işinin yapıldığı sunucularıdır. Kısacası Master’a yazılan tüm datanın, ‘N’ sayıdaki slave’e replicaları oluşturulur. Bu tek yönlü bir iştir. Bu makalede, ben her bir redis sunucusunun, kendine ait tek bir server’a atanmasını ve master gibi çalışmasını ama herhangi bir redis sunucusunda oluşacak data değişikliğinin diğer sunuculara ait redis makinalarına da bildirilip, tüm datanın her sunucuda eşitlenmesini amaçladım. Kısacası yazma da dahil olmak üzere tüm yükü farklı makinalara atadım. Eğer Redis hakkında pek de bir fikriniz yok ise, öncelik ile bu makale serisini okumanızı tavsiye ederim.

Not: Sunuculardan birinin gitmesi ya da failover durumu ile sentinelleri bu makalenin konusu değildir. İstenir ise, herbir sunucu içinde master-slave yapısı oluşturulup, sentineller ile kontrol altına alınabilir.

Öncelikle gelin Redis neden kullanılır en temel ihtiyacı ile konuşalım. Diyelimki sitenize, anlık 10bin kişi geldi. İlgili datanın her bir client için DB’den çekilmesi, tam bir felaket senaryosudur. Bunun için Redis gibi Rem’de tutulan çok hızlı cevap veren bir cache yapısına ihtiyaç vardır. Aksi takdirde DB serverların CPU’su tavan yapacak ve bir süre sonra cevap veremiyecek bir hal alacaktır. Not: Redis config ayarları yapılır ise, ilgili datayı DB’de de tutmaktadır. Ama tabi ki hızdan feragat etmek gerekir.

Gelen client sayısı 50binlere çıktığı durumlarda, tek bir Redis sunucusu maalesef yeterli olmıyacaktır. Bugünkü makalenin konusu, her bir sunucuya bir Redis atanarak en az iki sunuculu bir yapıda, yani 2 redis makinasında, data tutarlılığını manuel olarak mikroservicesler ile sağlamaktır. Anlık data girişinin ve düzenlemenin çok olmadığı Portallar gibi yapılarda, redis sunucuları arasında data tutarlılığı microservisler ile yapılabilir. Bu makale, tamamen size farklı bir bakış açısı sağlamak amacı ile yazılmıştır. “Olaylar gerçek ama kişiler tamamen hayal ürünüdür” :)

İsterseniz gelin önce örnek amaçlı .Net Core Mvc bir haber sayfasını hızlıca kodlıyalım. Uygulamanın tamamı Macbook macOS High Sierra üzerinde yazılacaktır.

1-) Öncelikle makinanızda yok ise Homebrew, aşağıdaki komut ile kurulur.

2-) Makinanızda Redis yok ise, aşağıdaki komut ile kurulur.

3-) Aşağıdaki komut ile Redis Server ayağı kaldırılır.

4-) Redis Test: Aşağıdaki komut ile Redis Client’da, “Pong” cevabını almanız gerekmektedir.

Sıra geldi alttaki komut ile .Net Core Mvc “RedisNews” projesini oluşturmaya.

.Net Core Proje içinde, Redis’in kullanılabilmesi için “ServiceStack.Redis.Core” aşağıdaki komut ile eklenir. Daha farklı birçok kütüphane vardır.

appsettings.json: Proje içinde redis ile ilgili tanımların yapıldığı config dosya.

Projede kullanılan dotnet versiyonu aşağıdaki gibidir:

Artık .NetCore’da default olarak “https” ile yayım yapılmaktadır. Benim makinada neden ise, default gelen “https://localhost/5001” portunda hata oluşmaktadır. Belki aynı hata sizde de olur diye, kendimce çözümü aşağıda paylaştım:

Program.cs: UseUrls() methodu aşağıdaki gibi eklenir.

Models/News: Sayfaya basılacak News(Haber) modeli.

Controllers/HomeController(Constructor):

  • HomeController constructor’da dependency injection ile “(IConfiguration configuration)” almaktadır.
  • “_configuration”: Redis için gerekli host ve port bilgilerinin “appsettings.json“‘den okunabilmesi için tanımlanır.
  • “conf” : Redis için kullanılan, genel tanımlı “RedisEndpoint” konfigürasyonudur.
  • Proje ilk kez çalıştırılıyor ise, News model boş gelecektir. ==> “if (model == null)”.
  • İlgili static model boş ise, constructor’da yani proje ilk ayağa kalkar iken, 1 kerelik örnek amaçlı manuel doldurulur. UpdatedDate, en başta null olarak atanmıştır. Son olarak “IsError” değeri default olarak “false” atanmıştır.

Controllers/HomeController(AddRedisCache()):

  • “using (IRedisClient client = new RedisClient(conf))” : İlgili Redis Client tanımlanır.
  • “var redisNews = client.Get<News>(cacheKey)” : İstenen haber (cacheKey) ile redisden çekilir.
  • “client.Set<News>(cacheKey, news)” : Eğer redis’de hiç kayıt yok ise, yeni girilen “News” datası tanımlı redise’e atılır.
  • “return redisNews” : Redisden çekilen News model, view’da kullanılmak üzere geri dönülür.

Not: Redis’de ilgili kayıt,”RedisNews” keyword’ü ile aşağıda görüldüğü gibi “string” olarak tutulmaktadır. İlgili data Redis’den “Get” methodu ile çağrılabilmektedir.

Index(): Aşağıda görüldüğü gibi Index sayfasına gitmeden ilgili data, “AddRedisCache()” methodu ile Redisden çekilmekte ve ilgili view’a gönderilmektedir.

Index.cshtml: Aşağıda görüldüğü gibi ilgili “News” model, Redisden alınarak ekrana basılmıştır. Haber tarihi, var ise son güncelleme tarihi(UpdatedDate), yok ise yaratılma tarihi (CreatedDate) olarak basılır.

wwwroot/css/table.css: Sayfada kullanılan custom Tablo css’i.

Şimdi sıra bu haberi güncelleyen, bir Admin sayfasının yapımında:

Startup.cs/Configure: .Net Core Mvc ortamında routing, “Index” ve “Admin” sayfaları için aşağıdaki gibi yapılır.

Admin(): Admin sayfasına gitmeden önce güncellenecek data, Redis’den “AddRedisCache()” methodu ile çekilip ilgili View’a gönderilir. Burada kullanılan “IsError” parametresi, ilerde anlatılacak olan data tutarlılığının sonucunu tutmaktadır.

Admin.cshtml: Aşağıda görüldüğü gibi bir <form> kontroller yapılmıştır. Sayfa post yani güncelleme işleminde, “UpdateNews()” methoduna gidilir. Alınan “@model News” datasının, güncellenmesi amacı ile Html Input elementlere basılır. Tüm Html elementlerin “name” değerleri, “News” modelde ait oldukları propertyler ile aynı olmak zorundadır. Sayfanın başındaki “@if(Model.IsError)”, ilerde anlatılacak olan data tutarlılığının bozulması durumunda, ekranda gösterilecek olan uyarı mesajının bir koşuludur. İlgili hata mesaji ekrana, “<script>alert(“Kayıtda tutarsızlık bulunmaktadır!”);</script>” komutu ile ekrana basılır.

*Dikkat edilir ise düzenlenecek haber’in resim bilgisi, ayrıca bir hidden alanda tutulmaktadır.”<input type=”hidden” name=”Image” value=”@Model.Image”>”==> Aksi takdirde, güncellenecek haberin resmi değişmez ise, haberin eski image datası elde olmadığı için boş olarak güncellenecektir.

* Sayfada yazılan UpdatedDate tarihi de, ayrıca bir hidden alanda tutulmaktadır. “<input type=”hidden” name=”UpdatedDate” value=”@Model.UpdatedDate”>”

Controllers/HomeController(UpdateNews()): Amaç, değiştirilen News datasının tüm Redis sunucularında güncellenmesidir.

  • CheckDataStable(client, news.UpdatedDate)” : Güncellenmek istenen datanın, makalenin hemen devamında anlatılacak olan, :) geçerli yani tutarlı bir kayıt olup olmadığı kontrol edilmiştir. Kayıdın tutarlı olmaması durumunda, kaydetme işlemi durdurulur ve “Admin()” action’ına “isError=true” şeklinde bir atama yapılarak yönlenilir. Böylece Admin sayfasına kaydetme işlemi olmadan tekrardan geri dönülür ve yukarıda görülen uyarı mesajı alınır.
  • “(NewsImage != null && NewsImage.Length > 0)” : Eğer yeni bir resim yüklenmiş ise “wwwroot/images” altına tekil bir guid adı ile Upload edilir.
  • “client.Set<News>(“RedisNews”, news)” : Bulunulan sunucudaki redis, güncellenen haberin(News), “UpdatedDate” alanı da set edilerek kaydedilir.
  • “var newsJson = JsonConvert.SerializeObject(news)” : Son güncel “News” datası, string’e çevrilir.
  • “client.PublishMessage(“News”, newsJson)” : Değişen “News” datasını diğer redis sunucularında da değişmesi için, ileride yazılacak olan “News” kanalını dinleyen, Redis Pub/Sub Microservices’ine “Publish” edilir.
  • ” return RedirectToAction(“Index”)” : Tüm güncelleme ve bildirim işlemleri bitince, “Index” sayfasına yönlenilir.

*Home/ CheckDataStable(): Geldik kaydın tutarlı olmasının ne anlama geldiğine: Diyelim ki biz haber admin sayfasına girdik ve kayıtla ilgili bilgileri değiştirmeye başladık. Bizim kaydetme işleminden önce, bir başka kullanıcı aynı haberi değiştirdi ve kaydetti. Bu durumda, bizim  an itibari ile güncellemeye çalıştığımız haber eski ve yanlış bir haberdir. İşte bu durumda, güncelleme amaçlı çekilen datanın “UpdatedDate”‘i ile kaydetmeden hemen önce çekilen Haberin “UpdatedDate”‘i karşılaştırılır. En başta çektiğimiz güncelleme tarihi, aslında bir başka kullanıcı tarafından yeni bir güncelleme yapılarak değiştirilmiştir. Bu iki tarih birbiri ile örtüşmediği için, elimizdeki haber datası aslında artık eski bir datadır. Bu durumda, bir başka kişinin yaptığı değişikliğin ezilmemesi adına, yapılan işleme devam edilmez ve uyarı mesajı verilir. Bu kontrol, başka sunucular üzerinden aynı haberin değiştirilmesi durumu için de geçerlidir. Çünkü Redis Pub/Sub ile ilgili kaydın, UpdatedDate’i de güncellenmiştir.

RedisNewsServices: Yeni bir.NetCoreConsole Applicationdır. Amacı “News” kanalını dinleyip, güncellenen News datasını, diğer Redis sunucularında da “UpToDate” yani güncel hale getirmektir. Aslında bir Microservisden başka birşey değildir. İlk olarak aşağıdaki kütüphaneler, projeye eklenir.

Program.cs: 

  • “using (sub = client.CreateSubscription())” : İlgili “News” kanalını dinleyecek Subscriber yaratılır.
  • “sub.OnMessage += (channel, news) =>” : Mesaj gelmesi durumunda, dinlenen (News)channel’ı ve alınacak News datası parametrik olarak tanımlanır.
  • “News _news = JsonConvert.DeserializeObject<News>(news)” : String olarak gelen data, deserialize edilerek “News” sınıfına dönüştürülür.
  • Sıra geldi güncellenen News datasının tüm Redis sunucularında da güncellemeye. İlgili tüm redis sunucularına IPleri ile erişilip güncellenir.
    • “var conf2 = new RedisEndpoint() { Host = “10.211.55.9”, Port = 6379 }” ==> “1. Redis sunucusuna erişilecek config tanımlanır.” Bu ilgili local makina da olabilir.
    • “using (IRedisClient clientServer = new RedisClient(conf2))” ==> Tanımlı Redis’e bağlanılır.
    • “clientServer.Set<News>(“RedisNews”, _news)” ==> Gelen “News” datası “RedisNews” keyword’ü ile güncellenir.
    • Bu işlemlerin aynısı bir sonraki sunucu içinde yapılır.

Not: İlgili Redis sunucularında ==>”redis-cli” yani client tarafında ==> “CONFIG SET protected-mode no” komutu çalıştırılarak, uzaktan erişim izninin verilmesi gerekmektedir.

Böylece değişen haber datası tüm redis sunucuları içinde güncellenmiş olunur. Sayfaya yeni giren ya da ilgili pencereyi güncelleyen client, en son güncel haber bilgisini, kendi server’ına atanmış Redis makinası üzerinden almış olacaktır.

Geldik bir makalenin daha sonuna yeni bir makalede görüşmek üzere hoşçakalın.

Source Code : https://github.com/borakasmer/RedisDataConsistencyWithMicroservices

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

1 Cevap

  1. ishak akdaş dedi ki:

    Selamlar hocam,
    Öncelikle severek takip ediyoruz :)
    Gerçekten harika işlere imza atıyorsunuz.
    Videonuzu daha önce izlemiştim ama bugün tekrar izlemem gerekti. üzerine istişare edelim diye buradan yazıyorum. Haddimi asmışsam affola :)
    Her iki redis sunucumuzu master yaptığımız zaman bir kısımdaki redis kapanırsa sistem durma noktasına gelebilir. Durum böyle olmaması adına hazır elimizde 2 redis sunucusu varken 1 redis sunucusunu Master, 1 redis sunucusunuda slave yaparsak bu sorunu minimize hale getirebiliriz. Önce Sentinel ip adresine istek atar master sunucuyu öğreniriz. Bir sorun olması durumunda Sentinel bizim yerimize master sunucuyu ayarladığı için herhangi bir işlem yapmadan kesintiyi minimize hale getirebiliriz diye düşünüyorum. Tabii Video epey eski olduğu için fikrinizde değişmiş olabilir :) Ben videoadaki düşüncelerinize binaen bu yorumu yapıyorum. Tekrar sevgiler.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.