Devnot

Redis ile Caching, Pub/Sub/Messaging ve Replication

Bu yazıda çalıştığım firmada etkin olarak kullandığımız ve üzerinde araştırmalar yaptığım, NoSQL veri yapısı yönetimi ve sunucusu olan Redis’in caching, pub/sub/messaging ve replication özelliklerinden bahsedeceğim.redis-logo

REmote DIctionary Server (Uzak Sözlük Sunucusu), yani REDIS ilk olarak Salvatore Sanfilippo tarafından geliştirilen hızlı, açık kaynak (open source), bellek içi (in-memory, bilgi sadece RAM’de tutulur, bilgisayar kapatılınca kaybolur), anahtar-değer (key-value pair) veri deposudur.

Redis verileri sunucunun memory kaynağında saklayarak erişim sağlamakla birlikte istenildiği takdirde diske yazma özelliği de vardır (Bkz. https://redis.io/topics/persistence).

Redis, kaynağından bağımsız olarak verileri 3 farklı şekilde tutabilmektedir. Bunlar:

1) RedisNativeClient: Düşük seviye byte[] dizisi cinsinden veri tutar.
2) RedisClient: string olarak veri tutar.
3) RedisTypedClient: redis.As<T>() şeklinde verilen T modelinde, .Net POCO tipi veri tutma işlemini yapar.

Bu yöntemlerden bazılarını aşağıdaki örneklerde kullanacağız. Örnekleri .NET Core üzerinde yapacağız. Örnekleri siz de kendi bilgisayarınızda yapmak isterseniz altta belirttiğim kurulumları yapmanız gerekecek:

* redis-cli https://github.com/microsoftarchive/redis/releases son sürümünü indirebilirsiniz,

* Redis Desktop Manager veya Another Redis Desktop Manager buradan kaydettiğimiz bilgileri arayüzde görmek için.

Redis ile ilgili indirmeleri ve kurulumları yaptıktan sonra Hizmetler’den (Services) çalıştığını kontrol edebilirsiniz.

cache1

.Net için gerekli olan ServiceStack, .Net Core için ServiceStack.Core paketlerini Nuget Manager aracılığıyla projenize dahil edebilirsiniz. Projede ServiceStack.Redis’den yararlanacağız.

Redis ile Caching

Sürekli olarak değişmeyen verilerin kullanımı ve yönetimi için tekrar tekrar aynı verileri çekerek projeye yük getirmek yerine, bu verileri bir defa çekip sonra bellekte tutmak performans açısından oldukça hız sağlayacaktır. Bunun için veritabanında bulunan ve her zaman değişmeyecek olan User listemizi Redis ile bellekte tutacağız. Redis bize verileri tutmamız için 16 farklı db sunar. Bu verileri görmek için Redis Desktop Manager veya Another Redis Desktop Manager ı kullanacağız.

Veritabanından çekilecek olan bilgileri temsilen bir User listesi oluşturduk.

 
        public class UserManager
        {
            private static List users = new List()
            {
                new User() {Id = 1, FirstName = "Özgün", LastName = "Doğan"},
                new User() {Id = 1, FirstName = "Özgül", LastName = "Arabacı"}
            };

            public static List GetAllUsers()
            {
                return users;
            }
        }

Şimdi Redis bağlantımızı yapıp çektiğimiz users bilgisini önce bellekte var mı diye kontrol edip yoksa Lists metodunu kullanarak belleğe ekleyeceğiz;

  
        public class RedisService
        {
            private IRedisClient _client;
            private IRedisClientsManager _clientsManager;

            public RedisService()
            {
                _clientsManager = new BasicRedisClientManager("localhost?db=1")
                _client = _clientsManager.GetClient();
            }

            public List GetCurrentUsers(string key)
            {
                if (_client.ContainsKey(key))
                {
                    return _client.Get & lt; List & gt; (key);
                }
                var currentUsers = UserManager.GetAllUsers();

                SetLists(key, currentUsers, DateTime.Now.AddDays(1), _client);
                return currentUsers;
            }

            public void SetLists(string key, IEnumerable list, DateTime expireTime, IRedisClient _client)
            {
                IRedisTypedClient redisTyped = _client.As();
                var currentList = redisTyped.Lists[key];
                var cachedList = currentList.Concat(list).ToList();
                _client.Set(key, cachedList, expireTime);
            }
        }

Yukarıda RedisService’in constructor metodunda “localhost?db=1″ ifadesiyle 1. database’i kullanacağımızı belirttik. IRedisClientsManager’ı pub/sub/ messaging için birazdan kullanacağız. Metodu tekrar kullanılabilirlik açısından generic şekilde tanımladık. GetCurrentUsers metoduna bir key vererek o key’e ait bellekte tuttuğumuz bir değer var mı kontrolü yapıp yoksa SetLists metoduyla belleğe 1 günlüğüne ekledik. BasicRedisClientManager class’ı RedisManagerPool, PooledRedisClientManager gibi IRedisClientsManager interface’inden türemiştir. IoC için kullanımını kolaylıkla sağlayabiliriz.

 
        class Program
        {
            static void Main(string[] args)
            {
                RedisService service = new RedisService();
                var userList = service.GetCurrentUsers("urn:current:users");
            }
        }

cache2

Verdiğimiz “urn:current:users” key’i ile bellekte bu veriyi tutacağız. Buradaki “:” ifadesi klasörleme mantığında çalışmaktadır. Yani db1>urn>current>users> “urn:current:users” değeri bulunur.

Redis ile Pub/Sub Messaging

Pub/sub client’ların birbirlerinin yayınladıkları bilgilere ulaşmaları için kullanılan bir yöntemdir. Bir client belirli kanallara üye olup, bu kanallara mesaj gönderebilir. Bu kanallara üye olan clientlar da gönderilen mesajlara ulaşabilir veya client başka client’ın mesajlarına ulaşabilir. Aktif üyelikleri bulunduğu sürece her client bildirimleri almaya devam eder.

Bir veya birden fazla kanala üye olup o kanala gelen mesajları konsola yazdırdığımız küçük bi uygulama yapalım.

Yukarıda oluşturduğumuz RedisService sınıfına Subscribe ve PublishMessage metodlarını ekleyelim.

        public void PooledSubscribe(string channel1, string channel2)
        {
            new RedisPubSubServer(_clientsManager, channel1, channel2)
            {
                OnMessage = (channel, msg) = > "Received '{0}' from '{1}'".Print(msg, channel)
            }.Start();
        }

        public void PublishMessage(string channel, string message)
        {
            _redisClient.PublishMessage("channel-2", "sabit mesajımız Service Stackten");
            _redisClient.PublishMessage(channel, message);
        }

PooledSubscribe metoduyla iki kanala üye olduk. Aynı yerde sadece bir kanala da üye olabiliriz. PublishMessage metodunda ise vereceğimiz kanal ismine göre kanala mesaj gönderiyoruz ve dinlediğimiz channel-2 ye de bir mesaj gönderiyoruz. Mesajları OnMessage olayını tetikleyerek Print ile konsola yazdırıyoruz. Program.cs’de ilgili kodları çalıştıralım.

        class Program
        {
            static void Main(string[] args)
            {
                RedisService service = new RedisService();
                var userList = service.GetCurrentUsers("urn:current:users");

                service.PooledSubscribe("channel-1", "channel-2");
                service.PublishMessage("channel-1", JsonConvert.SerializeObject(userList));
            }
        }

Console çıktısı;

cache3

Evet, Redis’te caching ve pub/sub messaging işlemlerini temel bazda örneklendirdik. StackExchange ile olan kullanım ve birkaç örnekle daha yazılım mimarisine daha uygun olarak yazdığım proje, github hesabımda mevcuttur.

Redis Master/Slave Replication

Replication yani ‘çoğaltma’ özelliği, bir Redis sunucusunun başka bir Redis sunucusuna birebir kopyalanması işlemini temel alır. Master’da yapılan güncellemeler, slave’de de gerçekleşir. Redis’in 2.6 sürümünden itibaren sadece veri okuma amaçlı default olarak readonly’dir. Kurulum dosyasında bulunan redis.windows.conf dosyasında “slave-read-only yes” ifadesini no olarak değiştirebiliriz ancak bu master ve slave arasında tutarsızlık oluşturabileceği nedeniyle tavsiye edilmez. Slave’in yazma özelliğini aktif ettiğimiz takdirde oluşan değişiklikler master’a aktırılmaz.

Özellikleri;

* Replication işlemi kullanıcı müdahalesi gerekmeden otomatik gerçekleşir.
* Herhangi bir ağ kesintisi durumunda slaveler otomatik olarak master’a bağlanmayı dener ve senkronize olurlar.
* Slaveler de birbirlerinden oluşturulabilir.

Örneğin; A master sunucu, B A’dan oluşan slave, C B’den oluşan slave olsun

A—>B—>C

B yazılabilir bir slave olduğunda, onun üzerindeki değişiklikleri C görmez. C bunun yerine A master sunucusunun kopyasını taşımaya devam eder.

Neden ihtiyaç duyulur?

Sunucumuzda çok fazla operasyon olduğunda okuma işlemlerimizi slavelere vererek veri kontrolü, erişim güvenliği sağlamış oluruz. Redis’ in Sentinel özelliğiile master slave in herhangi bir sebepten dolayı çalışmama durumunda, belirlenen algoritmaya göre slavelerden bir tanesi master seçilerek projenin çalışmasını sürdürmesini sağlar.

Nasıl kullanılır?

Bilgisayarımıza ikinici bir redis-cli yüklüyoruz, bunu slave olarak tanımlayacağız.
redis1

Slave olarak değiştireceğimiz redis klasöründeki redis.windows.conf dosyasında açıklama satırı olan slaveof kısmını (aşağıdaki ekran görüntüsünde bulunan)
“ slaveof 127.0.0.1 6379 ” ve portu 6380 olarak değiştirelim. Master portumuz 6379 olarak kaldı (bu değer default olarak tanımlıdır) ve slave’imizi de 6379’dan türetip portunu 6380 yaptık.

redi2

Her iki kurulum dosyasında bulunan redis-cli.exe dosyalarını çalıştırarak deneme yapalım.

redis3

Master sunucumuzda set ile birdegerkoy degerini 5 tanımladık ve slave olan 6380 portumuzda bu değeri get metodu ile okuyabildik.

Slave sunucumuzda set etme işlemi yapmak istiyoruz.

redis4

Default olarak read-only olan slave’imiz set işlemine izin vermedi.

INFO komutunu redis-cli.exe de çalıştırarak bulunduğumuz redis server ile ilgili bilgileri görebiliyoruz.

redis5

 

C# da Kullanımı için ;

Son olarak C# için bağlantı şekline bir örnek verelim. ServiceStack.Redis ile bağlantısı

 
using ServiceStack.Redis;
using System;

namespace RedisMasterSlave
{
    class Program
    {
        static void Main(string[] args)
        {
            IRedisClientsManager _clientsManager = new PooledRedisClientManager("localhost:6379","localhost:6380");
            var _redisClientMaster = _clientsManager.GetClient();
            var _redisClientSlave = _clientsManager.GetReadOnlyClient();
            _redisClientMaster.Set("birdegerkoy", 45);
            var deger = _redisClientSlave.Get("birdegerkoy");
            Console.WriteLine(deger);
            
        }
    }
}

_redisClientMaster ile masterda birdegerkoy degiskeni tanımladık. Bu değeri _redisClientSlave ile okuduk.
Görevi genel olarak okumak slave bağlantımızı GetReadOnlyClient() ile alabiliyoruz.

StackExchange.Redis ile yapılan bağlantı;

 
using StackExchange.Redis;
using System;

namespace RedisMasterSlave
{
    class Program
    {
        static void Main(string[] args)
        {

            Console.WriteLine("Value from Slave -- StackExchange.Redis ");

            var conn = ConnectionMultiplexer.Connect("localhost:6379,localhost:6380");
            var db = conn.GetDatabase(0);
            db.StringSet("baskadeger", 23);
            var fromReplica = db.StringGet("baskadeger", CommandFlags.PreferSlave);
            Console.WriteLine(fromReplica);
            Console.ReadLine();
        }
    }
}

Burada da
var fromReplica = db.StringGet(“baskadeger”, CommandFlags.PreferSlave);
satırında özellikle CommandFlags.PreferSlave ifadesiyle slave olan bağlantıdan veriyi almak istediğimizi belirtmiş olduk.

Bu yazıda, Redis’in caching, pub-sub messaging ve master-slave replication özelliklerinin genel kullanım amaçlarından bahsettik ve kullanım şekillerini inceledik.

Kaynaklar:
https://redis.io/topics/replication
https://www.programmerall.com/article/4414584805
http://cagataykiziltan.net/redis-replica
http://devnot.com/2018/rediste-master-slave-ve-sentinel-yapilari



Related Posts


?