From Redis to KeyDB with Nginx as load balancer and .Net Core

Part 1 - Using Redis Docker image with .Net Core API application

With my team we decided that we need a more robust approach to the implementation of our caching solution. Currently we are using a single Redis instance for caching, which is fine for part of our application. We wanted to move from a single instance of caching database to a cluster with at least two instances. We decided to research what alternatives there are and is there something that could fit more within our needs. We decided to look into KeyDB and what it could offer us.

What is Redis and what is KeyDB

For those of you who are not that familiar with Redis, it is a very popular maybe the most popular in-memory, key-value store. Redis is also open source. It is usually used for cache, message broker or as a database. You could find detailed information here.

C# Web Api app with Redis caching

We will start with the very basic out of the box web api application in .net core 3.1 using our favorite weather app which will use Redis for caching. We are going to tweak it a little bit in order to serve our needs.

{
...
,
"AllowedHosts": "*",
"ConnectionStrings": {
"redisServerUrl": "localhost:46379"
}
}
public void ConfigureServices(IServiceCollection services){
...
// Redis
services.AddStackExchangeRedisCache(options =>
{
options.Configuration =
this.Configuration.GetConnectionString("redisServerUrl");
});
}
running redis in docker
docker exec -it <container_name> /bin/bash
redis-cli
using Microsoft.Extensions.Caching.Distributed;
using System.Text.Json;
using System.Threading.Tasks;
namespace RedisVsKeyDb.Extensions
{
public static class CacheExtensions
{
public static async Task<T> GetCacheValueByKeyAsync<T>(this IDistributedCache cache, string key) where T : class
{
string result = await cache.GetStringAsync(key);
if (string.IsNullOrEmpty(result))
{
return null;
}
var deserializedResult = JsonSerializer.Deserialize<T>(result);
return deserializedResult;
}
public static async Task SetCacheValueAsync<T>(this IDistributedCache cache, string key, T value) where T : class
{
DistributedCacheEntryOptions cacheEntryOptions = new DistributedCacheEntryOptions();
// Remove item from cache
// cacheEntryOptions.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(60);
// Remove item from cache if unused for the duration
// cacheEntryOptions.SlidingExpiration = TimeSpan.FromSeconds(30);
string result = JsonSerializer.Serialize(value);
await cache.SetStringAsync(key, result.ToString(), cacheEntryOptions);
}
}
}
public class WeatherForecast
{
...
public bool Cached { get; set; }
}
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using RedisVsKeyDb.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace RedisVsKeyDb.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
private readonly IDistributedCache _cache;
public WeatherForecastController(ILogger<WeatherForecastController> logger, IDistributedCache cache)
{
_logger = logger;
_cache = cache;
}

public async Task<IEnumerable<WeatherForecast>> Get()
{
// Check if content exists in cache
var weatherResult = await _cache.GetCacheValueByKeyAsync<WeatherForecast[]>("weather-forecast");
if (weatherResult != null)
{
foreach (var item in weatherResult)
{
item.Cached = true;
}
return weatherResult;
}
var rng = new Random();
weatherResult = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
}).ToArray();

// Add to cache
await _cache.SetCacheValueAsync("weather-forecast", weatherResult);
return weatherResult;
}
}
}
“cached” is false because the data does not come from the cache
“cached” is true because the data comes from the cache