4
\$\begingroup\$

I have slightly altered Marc Gravells 2-tier cache code which can be seen here. I changed it to use Stackexchange.Redis instead of Booksleeve and also added some retry logic and serialization.

public class AzureRedisCache : ICacheService
{
    private static Lazy<string> _configurationOptions = new Lazy<string>(() => ConfigurationManager.AppSettings["redis_connection"]);
    private Cache localCache;
    private ISubscriber sub;
    private readonly string cacheInvalidationChannel = "invalidation";
    private static IDatabase Cache
    {
        get
        {
            return Connection.GetDatabase();
        }
    }

    private static readonly object nix = new object();

    private static readonly Lazy<ConnectionMultiplexer> LazyConnection
      = new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(_configurationOptions.Value));

    private static FixedInterval _retryStrategy = new FixedInterval(3, TimeSpan.FromSeconds(1));
    private static RetryPolicy _retryPolicy = new RetryPolicy<CacheTransientErrorDetectionStrategy>(_retryStrategy);

    public static ConnectionMultiplexer Connection
    {
        get
        {
            return LazyConnection.Value;
        }
    }

    private void Invalidate(RedisChannel channel, RedisValue payload)
    {
        string key = Encoding.UTF8.GetString(payload);
        var tmp = localCache;
        if (tmp != null) tmp.Remove(key);
    }       

    public AzureRedisCache()
    {
        HttpContext current = HttpContext.Current;
        if (current != null)
        {
            localCache = current.Cache;
        }
        else
        {
            localCache = HttpRuntime.Cache;
        }
        sub = Connection.GetSubscriber();
        sub.Subscribe(cacheInvalidationChannel, Invalidate);
    }

    public void Add(string key, object obj)
    {
        Add(key, obj, 60000);
    }

    public void Add(string key, object obj, int expiresInSeconds)
    {            
        _retryPolicy.ExecuteAction(() =>
        {                
            localCache.Add(key, obj ?? nix, null, DateTime.Now.Add(TimeSpan.FromSeconds(expiresInSeconds)), System.Web.Caching.Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
            Cache.StringSet(key, Serialize(obj), TimeSpan.FromSeconds(expiresInSeconds));
            Cache.Publish(cacheInvalidationChannel, key);
        });
    }

    public void Clear()
    {
        if (localCache != null)
        {
            IDictionaryEnumerator enumerator = localCache.GetEnumerator();
            while (enumerator.MoveNext())
            {
                localCache.Remove(enumerator.Key.ToString());
                RemoveByPattern("");
            }
        }
    }

    public object Get(string key)
    {
        //First check if the object is in local cache
        object found = localCache[key];
        if (found != null)
        {
            return found == nix ? null : found;
        }

        byte[] blob = null;
        object returnObject = null;
        _retryPolicy.ExecuteAction(() =>
        {
            blob = Cache.StringGet(key);
            if (blob != null)
            {
                returnObject = Deserialize<object>(blob);
            }
            else
            {
                returnObject = nix;
            }
            //Store in local cache so next time we can grab it from there
            localCache[key] = returnObject;
        });

        return ((returnObject ?? nix) == nix) ? null : returnObject;

    }

    public void Remove(string key)
    {
        try
        {
            _retryPolicy.ExecuteAction(() =>
            {
                localCache.Remove(key);
                Cache.KeyDelete(key);
                Cache.Publish(cacheInvalidationChannel, key);
            });
        }
        catch { }
    }

    public void RemoveByPattern(string _pattern)
    {
        foreach (var endpoint in Connection.GetEndPoints())
        {
            var server = Connection.GetServer(endpoint);
            foreach (var key in server.Keys(pattern: _pattern + "*"))
            {
                Remove(key);
            }
        }

    }

    static byte[] Serialize(object o)
    {
        if (o == null)
        {
            return null;
        }

        BinaryFormatter binaryFormatter = new BinaryFormatter();
        using (MemoryStream memoryStream = new MemoryStream())
        {
            binaryFormatter.Serialize(memoryStream, o);
            byte[] objectDataAsStream = memoryStream.ToArray();
            return objectDataAsStream;
        }
    }

    static T Deserialize<T>(byte[] stream)
    {
        if (stream == null)
        {
            return default(T);
        }

        BinaryFormatter binaryFormatter = new BinaryFormatter();
        using (MemoryStream memoryStream = new MemoryStream(stream))
        {
            T result = (T)binaryFormatter.Deserialize(memoryStream);
            return result;
        }
    }
}

I am also registering this using StuctureMap like this

For<ICacheService>().Singleton().Use<AzureRedisCache>();

Have I made any fatal mistakes in my code?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.