implementing out-of-process cache using Redis in windows azure implementing out-of-process cache using Redis in windows azure azure azure

implementing out-of-process cache using Redis in windows azure


If you want purely out-of-process, then it is pretty simple - something like the following, but noting that a BookSleeve is designed to be shared: it is fully thread-safe and works as a multiplexer - you shouldn't create / dispose them for every call. Note also that in this context I'm assuming you will handle serialization separately, so I'm simply exposing a byte[] API:

class MyCache : IDisposable{    public void Dispose()    {        var tmp = conn;        conn = null;        if (tmp != null)        {            tmp.Close(true);            tmp.Dispose();        }    }    private RedisConnection conn;    private readonly int db;    public MyCache(string configuration = "127.0.0.1:6379", int db = 0)    {        conn = ConnectionUtils.Connect(configuration);        this.db = db;        if (conn == null) throw new ArgumentException("It was not possible to connect to redis", "configuration");    }    public byte[] Get(string key)    {        return conn.Wait(conn.Strings.Get(db, key));    }    public void Set(string key, byte[] value, int timeoutSeconds = 60)    {        conn.Strings.Set(db, key, value, timeoutSeconds);    }}

What gets interesting is if you want a 2-tier cache - i.e. using local memory and the out-of-process cache, as now you need cache invalidation. Pub/sub makes that handy - the following shows this. It might not be obvious, but this would be doing a lot fewer calls to redis (you can use monitor to see this) - since most requests are handled out of the local cache.

using BookSleeve;using System;using System.Runtime.Caching;using System.Text;using System.Threading;class MyCache : IDisposable{    public void Dispose()    {        var tmp0 = conn;        conn = null;        if (tmp0 != null)        {            tmp0.Close(true);            tmp0.Dispose();        }        var tmp1 = localCache;        localCache = null;        if (tmp1 != null)            tmp1.Dispose();        var tmp2 = sub;        sub = null;        if (tmp2 != null)        {            tmp2.Close(true);            tmp2.Dispose();        }    }    private RedisSubscriberConnection sub;    private RedisConnection conn;    private readonly int db;    private MemoryCache localCache;    private readonly string cacheInvalidationChannel;    public MyCache(string configuration = "127.0.0.1:6379", int db = 0)    {        conn = ConnectionUtils.Connect(configuration);        this.db = db;        localCache = new MemoryCache("local:" + db.ToString());        if (conn == null) throw new ArgumentException("It was not possible to connect to redis", "configuration");        sub = conn.GetOpenSubscriberChannel();        cacheInvalidationChannel = db.ToString() + ":inval"; // note that pub/sub is server-wide; use                                                             // a channel per DB here        sub.Subscribe(cacheInvalidationChannel, Invalidate);       }    private void Invalidate(string channel, byte[] payload)    {        string key = Encoding.UTF8.GetString(payload);        var tmp = localCache;        if (tmp != null) tmp.Remove(key);    }    private static readonly object nix = new object();    public byte[] Get(string key)    {        // try local, noting the "nix" sentinel value        object found = localCache[key];        if (found != null)        {            return found == nix ? null : (byte[])found;        }        // fetch and store locally        byte[] blob = conn.Wait(conn.Strings.Get(db, key));        localCache[key] = blob ?? nix;        return blob;    }    public void Set(string key, byte[] value, int timeoutSeconds = 60, bool broadcastInvalidation = true)    {        localCache[key] = value;        conn.Strings.Set(db, key, value, timeoutSeconds);        if (broadcastInvalidation)            conn.Publish(cacheInvalidationChannel, key);    }}static class Program{    static void ShowResult(MyCache cache0, MyCache cache1, string key, string caption)    {        Console.WriteLine(caption);        byte[] blob0 = cache0.Get(key), blob1 = cache1.Get(key);        Console.WriteLine("{0} vs {1}",            blob0 == null ? "(null)" : Encoding.UTF8.GetString(blob0),            blob1 == null ? "(null)" : Encoding.UTF8.GetString(blob1)            );    }    public static void Main()    {        MyCache cache0 = new MyCache(), cache1 = new MyCache();        string someRandomKey = "key" + new Random().Next().ToString();        ShowResult(cache0, cache1, someRandomKey, "Initially");        cache0.Set(someRandomKey, Encoding.UTF8.GetBytes("hello"));        Thread.Sleep(10); // the pub/sub is fast, but not *instant*        ShowResult(cache0, cache1, someRandomKey, "Write to 0");        cache1.Set(someRandomKey, Encoding.UTF8.GetBytes("world"));        Thread.Sleep(10); // the pub/sub is fast, but not *instant*        ShowResult(cache0, cache1, someRandomKey, "Write to 1");    }}

Note that in a full implementation you probably want to handle occasional broken connections, with a slightly delayed reconnect, etc.