Use Redis as Cache Provider

Redis is an open source, in-memory advanced key-value store with optional persistence to disk. It’s high performance and support for 5 different data-sets makes Redis very attractive technology for different applications. One of the very good use case of Redis is using as cache server. In this article I will try to show how we can use Redis as simple key value cache server. I assume you already have Redis installed on your  machine. We will use the Redis client library named StackExchange.Redis.

Shouldn’t we need an interface that define what we can do with cache provider?

Lets start with a simple cache interface.

public interface ICacheStore
{
    bool Exists(string key);
    T Get<T>(string key);
    void Set<T>(string key, T value, TimeSpan expiredIn);
    void Remove(string key);

    Task<bool> ExistsAsync(string key);
    Task<T> GetAsync<T>(string key);
    Task SetAsync<T>(string key, T value, TimeSpan expiredIn);
    Task RemoveAsync(string key);
}

 

Okay we know the interface, lets write an implementation of ICacheStore on top of Redis

public class RedisCacheStore : ICacheStore
{
    private readonly IDatabase _database;
    private readonly ISerializer _serializer;
    private readonly CommandFlags _readFlag;

    public RedisCacheStore(IRedisConnectionWrapper connectionWrapper, IRedisServerSettings settings, ISerializer serializer)
    {
        _database = connectionWrapper.Database(settings.DefaultDb);
        _serializer = serializer;
        _readFlag = settings.PreferSlaveForRead ? CommandFlags.PreferSlave : CommandFlags.PreferMaster;
    }

    async Task<bool> ICacheStore.ExistsAsync(string key)
    {
        return await _database.KeyExistsAsync(key);
    }

    async Task<T> ICacheStore.GetAsync<T>(string key)
    {
        var result = await _database.StringGetAsync(key, _readFlag);

        return _serializer.Deserialize<T>(result);
    }

    async Task ICacheStore.SetAsync<T>(string key, T value, TimeSpan expiredIn)
    {
        await _database.StringSetAsync(key, _serializer.Serialize(value), expiredIn);
    }

    async Task ICacheStore.RemoveAsync(string key)
    {
        await _database.KeyDeleteAsync(key);
    }

    bool ICacheStore.Exists(string key)
    {
        return _database.KeyExists(key, _readFlag);
    }

    T ICacheStore.Get<T>(string key)
    {
        return _serializer.Deserialize<T>(_database.StringGet(key, CommandFlags.PreferSlave));
    }

    void ICacheStore.Set<T>(string key, T value, TimeSpan expiredIn)
    {
        _database.StringSet(key, _serializer.Serialize(value), expiredIn);
    }

    void ICacheStore.Remove(string key)
    {
        _database.KeyDelete(key);
    }
}

 

You might notice that RedisCacheStore has three dependencies. One is ISerializer, IRedisServerSettings and other is IRedisConnectionWrapper. Let first defined an interface for ISerializer and its Json Implementation. The implementation will use popular Newtonsoft.json Nuget package.

public interface ISerializer
{
    string Serialize<T>(T data);
    T Deserialize<T>(string serializedData);
}

public class JsonSerializer : ISerializer
{
    private readonly JsonSerializerSettings _settings;

    public JsonSerializer()
    {
        _settings = new Newtonsoft.Json.JsonSerializerSettings
                       {
                           ContractResolver = new CamelCasePropertyNamesContractResolver(),
                           NullValueHandling = NullValueHandling.Ignore,
                           DateTimeZoneHandling = DateTimeZoneHandling.Utc
                       };

        _settings.Converters.Add(new StringEnumConverter());
    }

    string ISerializer.Serialize<T>(T value)
    {
        return JsonConvert.SerializeObject(value, _settings);
    }

    T ISerializer.Deserialize<T>(string value)
    {
        return JsonConvert.DeserializeObject<T>(value, _settings);
    }
}

 

We will now define an interface for IRedisConnectionWrapper and its implementation. It is important to make sure that the implementation of IRedisConnectionWrapper registered as singleton in your IOC container.


public interface IRedisConnectionWrapper : IDisposable
{
    IDatabase Database(int? db = null);
    IServer Server(EndPoint endPoint);
    EndPoint[] GetEndpoints();
    void FlushDb(int? db = null);
}


public class RedisConnectionWrapper : IRedisConnectionWrapper
{
    private readonly IRedisServerSettings _settings;
    private readonly ILogger _logger;
    private readonly Lazy<string> _connectionString;

    private volatile ConnectionMultiplexer _connection;
    private readonly object _lock = new object();

    public RedisConnectionWrapper(IRedisServerSettings settings, ILogger logger)
    {
        _settings = settings;
        _logger = logger;

        _connectionString = new Lazy<string>(GetConnectionString);
    }

    private string GetConnectionString()
    {
        var con = ConfigurationManager.ConnectionStrings[_settings.ConnectionStringOrName];
        
        return con == null ? _settings.ConnectionStringOrName : con.ConnectionString;
    }

    private ConnectionMultiplexer GetConnection()
    {
        if (_connection != null && _connection.IsConnected) return _connection;
        
        lock (_lock)
        {
            if (_connection != null && _connection.IsConnected) return _connection;

            if (_connection != null)
            {
                _logger.Debug("Connection disconnected. Disposing connection...");
                _connection.Dispose();
            }

            _logger.Debug("Creating new instance of Redis Connection");
            _connection = ConnectionMultiplexer.Connect(_connectionString.Value);
        }

        return _connection;
    }

    public IDatabase Database(int? db = null)
    {
        return GetConnection().GetDatabase(db ?? _settings.DefaultDb);
    }

    public IServer Server(EndPoint endPoint)
    {
        return GetConnection().GetServer(endPoint);
    }

    public EndPoint[] GetEndpoints()
    {
        return GetConnection().GetEndPoints();
    }

    public void FlushDb(int? db = null)
    {
        var endPoints = GetEndpoints();

        foreach (var endPoint in endPoints)
        {
            Server(endPoint).FlushDatabase(db ?? _settings.DefaultDb);
        }
    }

    public void Dispose()
    {
        if (_connection != null)
        {
            _connection.Dispose();
        }
    }
}

 

RedisConnectionWrapper and RedisCacheStore dependent on IRedisServiceSettings Interface. Here is the definition of that interface and a configuration based implementation.


public interface IRedisServerSettings
{
    bool PreferSlaveForRead { get; }
    string ConnectionStringOrName { get; }
    int DefaultDb { get; }
}


public class RedisServerSettings : ConfigurationSection, IRedisServerSettings
{
    public static Lazy<IRedisServerSettings> Settings = new Lazy<IRedisServerSettings>(() => ConfigurationManager.GetSection("Redis.ServerSettings") as RedisServerSettings);

    [ConfigurationProperty("PreferSlaveForRead", IsRequired = false, DefaultValue = false)]
    public bool PreferSlaveForRead { get { return Convert.ToBoolean(this["PreferSlaveForRead"]); } }

    [ConfigurationProperty("ConnectionStringOrName", IsRequired = true)]
    public string ConnectionStringOrName { get { return this["ConnectionStringOrName"] as string; } }

    [ConfigurationProperty("DefaultDb", IsRequired = false, DefaultValue = 0)]
    public int DefaultDb { get { return Convert.ToInt32(this["DefaultDb"]); } }
}


Sample Configuration Section for RedisServerSettings provided below:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="Redis.ServerSettings" type="<full namespace>.RedisServerSettings, <assemblyname>" />
    </configSections>
    <Redis.ServerSettings DefaultDb="3" ConnectionStringOrName="RedisServer" PreferSlaveForRead="true" />
    <connectionStrings>
        <add name="RedisServer" connectionString="<your redis server ip address>:<port>,ConnectRetry=3,
            KeepAlive=180,name=<give a name>,allowAdmin=true,Password=<password if you secured the server>" />
    <connectionStrings>
</configuration>

To find out all connection options for redis using StackExchange.Redis check here.

 

Okay I have all the bits and pieces but how I can use them?

Very likely you already using an IOC container for your application. The code below showing using a NinjectModule to bind all cache related components in your IOC container. This code below should easily adopted for other DI frameworks like StructureMap, AutoFac etc.


public class CacheRepositoryModule : NinjectModule
{
    public override void Load()
    {
        Bind<IRedisServerSettings>().ToMethod(x => RedisServerSettings.Settings.Value).InSingletonScope();
        Bind<IRedisConnectionWrapper>().To<RedisConnectionWrapper>().InSingletonScope();
        Bind<ISerializer>().To<JsonSerializer>().InSingletonScope();
        Bind<ICacheStore>().To<RedisCacheStore>();
    }
}

That’s it you are now ready to use redis as cache in your application.

 

Show me some sample codes


public static class CacheStoreExtensions
{
    public static T Get<T>(this ICacheStore source, string key, TimeSpan time, Func<T> fetch) where  T : class 
    {
        if (source.Exists(key))
        {
            return source.Get<T>(key);
        }
        else
        {
            var result = fetch();

            if (result != null)
            {
                source.Set(key, result, time);
            }

            return result;
        }
    }
}

public class MenuController : Controller
{
    private const string Key = "MyApp:Categories:All";
    private readonly ICacheStore _cacheStore;
    private readonly ICategoryRepositor _categoryRepository;

    public MenuController(ICacheStore cacheStore, ICategoryRepository categoryRepository)
    {
        _cacheStore = cacheStore;
        _categoryRepository = categoryRepository;
    }

    [OutputCache(Duration = 3600)]
    [ChildActionOnly]
    public ActionResult CategoryMenu()
    {
        return PartialView(_cacheStore.Get(Key, TimeSpan.FromHours(1), () => _categoryRepository.GetAll()));
    }
}

Hope this post will help you implement Redis in your application. I will put the source code in git and put a link here later.

Update: A DotNet nuget package “Bolt.Cache” and “Bolt.Cache.Redis” is available. You can read more details here.

5 thoughts on “Use Redis as Cache Provider

  1. Can you explan the reason you check if (_connection != null && _connection.IsConnected) return _connection; twice, please ? Why didn’t you use double checked locking with if(_connection == null) lock(LockObject){if(_connection == null) … } for Singleton. Thanks.

    • Thanks for your comment.
      RedisConnectionWrapper planned to use as singleton and its responsibility to make sure there is only one connection created and shared in system. Now because RedisConnectionWrapper is singleton, there could be two possible scenarios. Lets say two consumers A and B using RedisConnectionWrapper.

      Scenario 1: Consumer A call GetConnection method very first time.
      – It will found that connection is null
      – request a lock
      – lock successful
      – inside the lock code block it found that _connection is null
      – create an instance of connection
      – return the connection
      – Consumer B then call GetConnection method
      – Consumer found that connection already created and connection is open
      – Existing connection returned for consumer B. Consumer B don’t need to acquire lock here.

      Scenario 2: Consumer A and Consumer B call GetConnection at same time and no other request call this method before
      – Consumer A found _connection is null. Consumer B also found _connection is null as they are running at same time
      – Consumer A and Consumer B request to acquire a lock and suppose consumer A win and Consumer B will wait until lock released
      – Consumer A found _connection is still null
      – Consumer A create the _connection and release the lock
      – As lock released and B was waiting for lock to be released so B will then acquire the lock and execute the code block inside lock
      – B then found that connection is not null
      – B then needs to check whether the _connection is still open or its closed by any chance (Which is possible e.g network goes down etc.)
      – B found that connection still open so it just get the existing connection and no new connection created.
      – if B found that connection not null but connection closed then it will dispose the connection and reestablish the connection

      Scenario 2 should explain why we need do the same checking again inside the lock. If I don’t do double checking inside lock then the system could create more than one connections and at some point you will find that maximum connection reached under heavy traffic to your application.

      Your second question is why I am not using only Null check. Well as I said it could possible the a connection established that means it not null but at some stage it can get disconnected. In that case we have to reestablish the connection.

      Hope this answers your question.

  2. Hello,
    Very nice article to encapsulate the redis implementation.
    One question I have is…Is there any reason why you used IDatabase and IServer in IRedisConnectionWrapper which are specific to StackExchange Redis Implementation (e.g compared to Service Stack Redis).
    I would like to make it so IRedisConnectionWrapper is not dependant on external interfaces. What do you think about it? If you agree may be a small sample? I would really appreciate it.

    • Hi Andy,
      Thanks for your comment. The responsibility of ConnectionWrapper is to make sure the application can share same connection instance not to hide StackExchange.Redis implementation. use of StachExchange.Redis is hidden behind ICacheStore. If you use ServiceStack.Redis library then you might not need this wrapper as they provide a class PooledRedisClientManager. But in case of StackExchange.Redis they explicitly say not to create connectionmultiplexer per operation. Read here.

      In case of ServiceStack.Redis I would not create a wrapper or factory. I would directly use IRedisClientsManager in RedisCacheStore class. Sample below:

      
      //IOC Binding. <a href="https://github.com/ServiceStack/ServiceStack.Redis">They</a> didn't mention this needs to be in singleton scope. 
      Bind<IRedisClientsManager>.To(c => 
          new RedisManagerPool(redisConnectionString));
      
      public class RedisCacheStore
      {
          private readonly IRedisClientsManager _mgr;
      
          public RedisCacheStore(IRedisClientsManager mgr)
          {
              _mgr = mgr;
          }
      
          public T Get<T>(string key)
          {
              using (var client = _mgr.GetClient())
              {
                  return client.Get<T>(key);
              }
          }
      }
      

      My personal view is if I use redis for only key value store then hiding the library behind another interface may be too much encapsulation. But its my personal view again. But as you asked for some sample code here are they:

      
      public interface IRedisClient
      {
          T Get<T>(string key);
          void Set<T>(string key, T value, TimeSpan expiredIn);
      }
      
      public class StackExchangeRedisClient : IRedisClient
      {
          private readonly IDatabase _database;
          private readonly ISerializer _serializer;
          private readonly CommandFlags _readFlag;
      
          public StackExchangeRedisClient(IRedisConnectionWrapper connectionWrapper, IRedisServerSettings settings, ISerializer serializer)
          {
              _database = connectionWrapper.Database(settings.DefaultDb);
              _serializer = serializer;
              _readFlag = settings.PreferSlaveForRead ? CommandFlags.PreferSlave : CommandFlags.PreferMaster;
          }
      
          public T Get<T>(string key)
          {
              return _serializer.Deserialize<T>(_database.StringGet(key, _readFlag));
          }
      
          public void Set<T>(string key, T value, TimeSpan expiredIn)
          {
              _database.StringSet(key, _serializer.Serialize(value), expiredIn);
          }
      }
      
      public class ServiceStackRedisClient : IRedisClient
      {
          private readonly IRedisClientsManager _mgr;
      
          public ServiceStackRedisClient(IRedisClientsManager mgr)
          {
              _mgr = mgr;
          }
      
          public T Get<T>(string key)
          {
              using (var client = _mgr.GetClient())
              {
                  return client.Get<T>(key);
              }
          }
      
          public void Set<T>(string key, T value, TimeSpan expiredIn)
          {
              using (var client = _mgr.GetClient())
              {
                  client.Set(key, value, expiredIn);
              }
          }
      }
      
      public class RedisCacheStore : ICacheStore
      {
          public RedisCacheStore(IRedisClient client)
          {
              ....
          }
      
          ....
      }
      
      

      Hope this answers your question.

      • Got it. That makes sense. Thank you!
        While I was trying to add a wrapper I am hard time to add wrapper on transactions. Goal is to easlity be able to switch the redis clients in future if needed but having a wrapper around transaction seems a bit tricky. Any suggestions?

Leave a reply to mruhul Cancel reply