30 Nisan 2019 Salı

.Net Core Uygulamasında Redis Cache Kullanımı


Bu yazıda Docker üzerinde bir Redis Container'ı koşturup, artından bir .Net Core Mvc projesinde nasıl kullanılacağını inceleyeceğiz. Redis (distributed cahce)' de amaç, bir web sayfası yada bir web API' dan gelen istekler olduğunda her seferinde servise ya da DB'ye gitmektense bu istekleri In Memory olarak yani ram'den karşılanması ve bu sayede performansın arttırılmasıdır.

Yazının tamamında kullanılacak teknolojiler;
  • Docker
  • Redis
  • .Net Core
Öncelikle eğer makinamızda Docker yok ise https://www.emrakin.com/2019/04/docker-nedir-neden-kullanlr-nasl.html bu adresten Docker kurulumu hakkında bilgi edinebilirsiniz.

Docker üzerinde bir Redis Container koştumak için aşağıdaki kodu kullanalar "6379" portundan ayağa kalkması sağlanır.
docker run -d --name Redis -p 6379:6379 redis

Ben .Net Core Mvc projesinde Redis için ServiceStack kullandım. İlgili kütüphaneyi https://www.nuget.org/packages/ServiceStack.Redis.Core bu linkten inceleyebilir ve Nuget ile projenize ekleyebilirsiniz.
PM> Install-Package ServiceStack.Redis.Core

Redis'in .Net Core projesinde kullanımı için Startup.cs'de AddDistributedRedisCache() methodu ile redisserver'ın IP ve Portu'nu tanımlıyoruz.
public void ConfigureServices(IServiceCollection services)
{
     services.Configure<CookiePolicyOptions>(options =>
     {
           options.CheckConsentNeeded = context => true;
           options.MinimumSameSitePolicy = SameSiteMode.None;
     });
     services.AddDistributedRedisCache(options =>
     {
            options.InstanceName = "RedisNetCoreSample";
            options.Configuration = "localhost:6379"; //Your Redis Connection
      });

      services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

AddDistributedRedisCache() methodunu Startup.cs' de kullanmak için Microsoft.Extensions.Caching.Redis'i Nugetten projenize dahil etmeniz gerekiyor.
PM> Install-Package Microsoft.Extensions.Caching.Redis

Models: Models klasörü altında Person.cs adında bir sınıf oluşturuyoruz.
public class Personal
{
     public int ID { get; set; }
     public string Name { get; set; }
     public string Surname { get; set; }
     public int Age { get; set; }
 }

Projemize RedisRepository adında bir klasör ekliyoruz. Bu klasör altında Redis'le ilgili işlemlerimizi bir interface yapı da tutacağız. RedisRepository klasörünün altında IRedisService adında bir interface oluşturuyoruz.
public interface IRedisService
{
     List<Person> GetAll(string cachekey);
     Person GetById(string cachekey);
 }

RedisRepository klasörü altına IRedisService.cs den  inheritance olmuş RedisManager.cs adında bir sınıf oluşturuyoruz.
public class RedisManager : IRedisService
{
    public List<Person> GetAll(string cachekey)
        {
            using (IRedisClient client = new RedisClient())
            {
                List<Person> dataList = new List<Person>();
                List<string> allKeys = client.SearchKeys(cachekey);
                foreach (string key in allKeys)
                {
                    dataList.Add(client.Get<Person>(key));
                }
                return dataList;
            }
        }

        public Person GetById(int personId, string cachekey)
        {
            using (IRedisClient client = new RedisClient())
            {
                var redisdata = client.Get<Person>(cachekey);

                return redisdata;
            }
        }
}

IRedisServise ve RedisManager'ı Startup.cs'e dahil etmemiz gerekiyor.

public void ConfigureServices(IServiceCollection services)
{
      services.Configure<CookiePolicyOptions>(options =>
      {
             options.CheckConsentNeeded = context => true;
             options.MinimumSameSitePolicy = SameSiteMode.None;
      });
      services.AddDistributedRedisCache(options =>
      {
            options.InstanceName = "RedisNetCoreSample";
            options.Configuration = "localhost:6379"; //Your Redis Connection
      });

      services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

      services.AddScoped<IRedisService, RedisManager>();
}

HomeController.cs/HomeController(): Öncelikle person sınıfı tipinde yeni bir kayıt oluşturup Redis cache'e atıyoruz.

- "cachedata.SetValue("Person"+personvalue.ID, personvalue);": Redis'de "Person+ID" key'ine karşılık value değeri olarak oluşturulan dataByte[] dizisi SetValue() methodu ile atıyoruz.

private readonly IRedisService _redisService;

public HomeController(IRedisService redisService)
{
      _redisService = redisService;
   
     using (IRedisClient client = new RedisClient())
     {
           if (client.SearchKeys("Person*").Count == 0)
           {
                 var personvalue = new Person();
                 personvalue.ID = 1;
                 personvalue.Name = "Emrah";
                 personvalue.Surname = "Akın";
                 personvalue.Age = 40;

                 var cachedata = client.As<Person>();
                 cachedata.SetValue("Person"+personvalue.ID, personvalue);

            }
      }
}

HomController.cs/Index: Redis'e "Person" key'i ile atılan tüm verileri getirmek için "Person*" ile çağırıyoruz.
public IActionResult Index()
{
      const string cacheKey = "Person*";
      var redisdata = _redisService.GetAll(cacheKey);

      return View(redisdata);
}

Index.cshtml:  View modelden gelenleri ekrana yansıtıyoruz

@model IEnumerable<NetCoreRedis.Models.Person>
@{
    ViewData["Title"] = "Index";
}

<table class="table">
    <thead>
        <tr>
            <th>
                ID
            </th>
            <th>
                Name
            </th>
            <th>
                SurName
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>

        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.ID)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Name)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Surname)
                </td>
                <td>
                    @Html.ActionLink("Details", "Home", new { id = item.ID }) |
                </td>
            </tr>
        }
    </tbody>
</table>

HomeController.cs/Detail: 
public IActionResult Detail(int Id)
{
    string cacheKey = "Person"+Id;
    var redisdata = _redisService.GetById(cacheKey);

     return View(redisdata);
}

Detail.cshtml: 
@model NetCoreRedis.Models.Person
@{
    ViewData["Title"] = "Detail";
}

<h3><b>Person:</b></h3>

<table class="table">
    <tr>
        <td>Ad:</td>
        <td>@Model.Name</td>
    </tr>
    <tr>
        <td>Soyad:</td>
        <td>@Model.Surname</td>
    </tr>
    <tr>
        <td>Yaş:</td>
        <td>@Model.Age</td>
    </tr>
</table>


Docker container olarak ayaklandırdığımız Redis cache üzerinden key value'larımızı görmek için terminal ekranına aşağıdaki kodu yazdıktan sonra "Keys *" komunu kullanabilirsiniz.

docker exec -it Redis redis-cli

Projenin bitmiş haline https://github.com/emrakin/.NetCore-Redis buradan ulaşabilirsiniz.

İlgili Kaynaklar:

Benzer Yazılar

10 yorum:

  1. Merhaba ;

    Aşağıdaki hatayı alıyorum. Sizin örneği indirdim gene aynı hatayı aldım.
    docker exec -it Redis redis-cli ile redis'e bağlanıyorum. Ama uygulamayı çalıştırınca aşağıdaki hatayı alıyorum.
    Firewall kapattım. 6379 portuna inbound outbound izin verdim. gene olmadı.
    Nedir problem çözemedim ?

    $ docker container ps
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    5f3fbd3fbb3f redis "docker-entrypoint.s…" 10 minutes ago Up 10 minutes 0.0.0.0:6379->6379/tcp Redis

    An unhandled exception occurred while processing the request.
    ExtendedSocketException: No connection could be made because the target machine actively refused it 127.0.0.1:6379
    System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot, SocketAddress socketAddress)

    RedisException: Exceeded timeout of 00:00:10
    ServiceStack.Redis.RedisNativeClient.SendReceive(byte[][] cmdWithBinaryArgs, Func fn, Action> completePipelineFn, bool sendWithoutRead) in RedisNativeClient_Utils.cs, line 649

    YanıtlaSil
    Yanıtlar
    1. 6379 dan telnet olabildiğim tek adres : 192.168.99.100
      docker is configured to use the default machine with IP 192.168.99.100 yazıyordu.
      Bu adresi projede options.Configuration = "192.168.99.100:6379" olarak yazdım. aynı hatayı alıyorum. 127.0.0.1:6379 nerden geliyor ?

      Sil
    2. Adres Startup.cs dosyasında

      services.AddDistributedRedisCache(options =>
      {
      options.InstanceName = "RedisNetCoreSample";
      options.Configuration = "localhost:6379"; //Your Redis Connection
      });

      Sil
  2. 127.0.0.1:6379 dan redis container ayakta ise ne yazarsan yaz hatta hiç yazma gene açlıyor. // options.Configuration = "xxxx";

    YanıtlaSil
    Yanıtlar
    1. Docker'ı ayaklandırdığın adres 0.0.0.0:6379->6379/tcp bumu? Yoksa 192.168.99.100:6379->6379 mu? Anladığım kadarıyla Redis'in koştuğu Docker containerın ip ile projede bağlanmak istenilen ip tutmuyor. Daha detayl ı anlatırsan yardımcı olmak isterim.

      Sil
    2. https://gist.github.com/bahmutov/f09b5895f5bb0f2a13f5

      https://stackoverflow.com/questions/32924675/unable-to-connect-to-dockerized-redis-instance-from-outside-docker/40581745

      Sil
  3. Merhaba ilginiz için teşekkürler. Docker erişmek ile ilgili sıkıntım yok. redis cli keys ve value lara da erişebiliyorum. Uygulamanız da çalışıyor. Ancak uygulamanız da options.Configuration için ne yazarsam yazayım gene çalışıyor. Hatta hiç yazmayayım uygulamanız gene çalışıyor. Ben başka bir şey yazmak istiyorum. Redis 0.0.0.0:6379->6379/tcp den ayakta.

    YanıtlaSil
    Yanıtlar
    1. Tamamdır şimdi anladım. Şöyle dener misin?

      aşağıda ki conf kısmına kendi ip ve portunu yazarak bağlantıyı istediğin gibi yönlendirebilirsin. Denersen ve dönüş yaparsan çok sevinirim.


      1- HomeController.cs

      var conf = new RedisEndpoint() { Host = "localhost veya ip adresi", Port = 6565 };

      using (IRedisClient client = new RedisClient(conf))
      {
      İlk kaydın atıldığı yer...
      }

      2-RedisManager.cs GetAll ve GetById nin altına

      var conf = new RedisEndpoint() { Host = "localhost veya ip adresi", Port = 6565};
      using (IRedisClient client = new RedisClient(conf))
      {
      ....
      }

      3- Startup.cs aşağıda ki kodu silebilirsin.

      //services.AddDistributedRedisCache(options =>
      //{
      // options.InstanceName = "RedisNetCoreSample";
      // options.Configuration = "localhost:6385"; //Your Redis Connection
      //});

      Sil
  4. Tmmdır, çok tşkler. Redis Maneger aşağıda ki gibi yaptım çalıştı. Şimdi Redis adresi bir önem arz etmeye başladı. Aslında bundan sonra ki hedefim uygulamanızı docker image yapıp sonra container olarak çalıştırıp, redis'i buradan kullanmak. Daha sonra container sayısını çoğaltıp nginx üzerinden load balance yapmak, sonra bir API Gateway koymak üzerinden yönlendirmek...vs :)

    public class RedisManager : IRedisService
    {
    RedisEndpoint conf;

    public RedisManager()
    {
    conf = new RedisEndpoint() { Host = "127.0.0.1", Port = 6379 };
    }

    public int SearchKeys(string pattern)
    {
    using (IRedisClient client = new RedisClient(conf))
    {
    return client.SearchKeys("Person*").Count;
    }
    }

    public void Add(string key, Person person)
    {
    using (IRedisClient client = new RedisClient(conf))
    {
    var cachedata = client.As();
    cachedata.SetValue(key, person);
    }
    }

    public List GetAll(string cachekey)
    {
    using (IRedisClient client = new RedisClient(conf))
    {
    List dataList = new List();
    List allKeys = client.SearchKeys(cachekey);
    foreach (string key in allKeys)
    {
    dataList.Add(client.Get(key));
    }
    return dataList;
    }
    }

    public Person GetById(string cachekey)
    {
    using (IRedisClient client = new RedisClient(conf))
    {
    var redisdata = client.Get(cachekey);

    return redisdata;
    }
    }
    }

    YanıtlaSil
  5. container yaptığım uygulamanızdan host dosyasına yazdıgım Ethernet adapter vEthernet (DockerNAT) ip adresi üzerinden redise eriştim. Bunu dışarıdan environment name olarak versek daha saglıklı olur tabii. tskler.

    YanıtlaSil