26

Does anyone have a solid pattern fetching Redis via BookSleeve library?

I mean:

BookSleeve's author @MarcGravell recommends not to open & close the connection every time, but rather maintain one connection throughout the app. But how can you handle network breaks? i.e. the connection might be opened successfully in the first place, but when some code tries to read/write to Redis, there is the possibility that the connection has dropped and you must reopen it (and fail gracefully if it won't open - but that is up to your design needs.)

I seek for code snippet(s) that cover general Redis connection opening, and a general 'alive' check (+ optional awake if not alive) that would be used before each read/write.

This question suggests a nice attitude to the problem, but it's only partial (it does not recover a lost connection, for example), and the accepted answer to that question draws the right way but does not demonstrate concrete code.

I hope this thread will get solid answers and eventually become a sort of a Wiki with regards to BookSleeve use in .Net applications.

-----------------------------

IMPORTANT UPDATE (21/3/2014):

-----------------------------

Marc Gravell (@MarcGravell) / Stack Exchange have recently released the StackExchange.Redis library that ultimately replaces Booksleeve. This new library, among other things, internally handles reconnections and renders my question redundant (that is, it's not redundant for Booksleeve nor my answer below, but I guess the best way going forward is to start using the new StackExchange.Redis library).

Community
  • 1
  • 1
Ofer Zelig
  • 17,068
  • 9
  • 59
  • 93
  • Use a heartbeat thread with period < redis-conf#timeout . http://www.youtube.com/watch?v=jr0JaXfKj68 . And No, that is not the end of it. Redis may (it is possible) drop the client. Reconnect logic is a requirement and there is NO way around it. – alphazero Dec 29 '11 at 03:07
  • Yes, I need the reconnect logic indeed... in a robust & clean code... BTW nice youtube video. First time I encounter this in SO. – Ofer Zelig Dec 30 '11 at 12:41
  • 1
    Brilliant - the answer update comes on the same day we see the issue for the first time. – Anthony Mar 21 '14 at 12:02

3 Answers3

29

Since I haven't got any good answers, I came up with this solution (BTW thanks @Simon and @Alex for your answers!).

I want to share it with all of the community as a reference. Of course, any corrections will be highly appreciated.

using System;
using System.Net.Sockets;
using BookSleeve;

namespace Redis
{
    public sealed class RedisConnectionGateway
    {
        private const string RedisConnectionFailed = "Redis connection failed.";
        private RedisConnection _connection;
        private static volatile RedisConnectionGateway _instance;

        private static object syncLock = new object();
        private static object syncConnectionLock = new object();

        public static RedisConnectionGateway Current
        {
            get
            {
                if (_instance == null)
                {
                    lock (syncLock)
                    {
                        if (_instance == null)
                        {
                            _instance = new RedisConnectionGateway();
                        }
                    }
                }

                return _instance;
            }
        }

        private RedisConnectionGateway()
        {
            _connection = getNewConnection();
        }

        private static RedisConnection getNewConnection()
        {
            return new RedisConnection("127.0.0.1" /* change with config value of course */, syncTimeout: 5000, ioTimeout: 5000);
        }

        public RedisConnection GetConnection()
        {
            lock (syncConnectionLock)
            {
                if (_connection == null)
                    _connection = getNewConnection();

                if (_connection.State == RedisConnectionBase.ConnectionState.Opening)
                    return _connection;

                if (_connection.State == RedisConnectionBase.ConnectionState.Closing || _connection.State == RedisConnectionBase.ConnectionState.Closed)
                {
                    try
                    {
                        _connection = getNewConnection();
                    }
                    catch (Exception ex)
                    {
                        throw new Exception(RedisConnectionFailed, ex);
                    }
                }

                if (_connection.State == RedisConnectionBase.ConnectionState.Shiny)
                {
                    try
                    {
                        var openAsync = _connection.Open();
                        _connection.Wait(openAsync);
                    }
                    catch (SocketException ex)
                    {
                        throw new Exception(RedisConnectionFailed, ex);
                    }
                }

                return _connection;
            }
        }
    }
}
Ofer Zelig
  • 17,068
  • 9
  • 59
  • 93
  • You're code is doing the double-check locking thing which is [actually broken](http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html) (at least on the Java platform, but I'd doubt the C# environment is radically different) – Alex Popescu Jan 08 '12 at 15:50
  • @AlexPopescu, the .Net CLR resolves issues related to using _Double-Check Locking_ that are common in other environments. Please see: http://msdn.microsoft.com/en-us/library/ff650316.aspx . But thanks to your comment, I noticed I should add the _volatile_ keyword to __instance_. Thanks! – Ofer Zelig Jan 09 '12 at 06:49
  • I was trying out your solution and on average it's 120ms slower than my bad hack of just returning a new connection each time. http://pastie.org/3965493. Are you using this code today or have you done any modifications on it? – Bjarki Heiðar May 25 '12 at 10:15
  • @BjarkiHeiðar I'm using this code today. What line of code causes the performance issue? – Ofer Zelig May 25 '12 at 15:55
  • I did not check witch line it was. what I did was create a small client that will call my function 20 times and timed it. here is the function I was calling. http://pastie.org/3988120 . By changing the using statement to use you connection it added around 100ms to per requst. Could be that I'm using it incorrectly but if I am I would appreciate an example of how to use this. – Bjarki Heiðar May 29 '12 at 11:22
  • How do you handle when Redis goes down for a longer period of time? (i.e. did you implement some kind of "alive" check as mentioned?) – Mike G Sep 18 '12 at 00:13
  • BookSleeve will eventually turn its state to `Closing` or `Closed` and my code will just enlive it upon the subsequent call. – Ofer Zelig Sep 18 '12 at 14:49
  • @BjarkiHeiðar did you end up working out if this was slower than the naieve method or if you just had an issue in your implementation? – rejj Sep 27 '12 at 01:20
  • @rejj I'm just using my own implementation that uses a new connection each time it's faster for me than to use this implementation. :/ – Bjarki Heiðar Sep 27 '12 at 08:55
2

With other systems (such as ADO.NET), this is achieved using a connection pool. You never really get a new Connection object, but in fact get one from the pool.

The pool itself manages new connections, and dead connections, independently from caller's code. The idea here is to have better performance (establishing a new connection is costy), and survive network problems (the caller code will fail while the server is down but resume when it comes back online). There is in fact one pool per AppDomain, per "type" of connection.

This behavior transpires when you look at ADO.NET connection strings. For example SQL Server connection string (ConnectionString Property) has the notion of 'Pooling', 'Max Pool Size', 'Min Pool Size', etc. This is also a ClearAllPools method that is used to programmaticaly reset the current AppDomain pools if needed for example.

I don't see anything close to this kind of feature looking into BookSleeve code, but it seems to be planned for next release: BookSleeve RoadMap.

In the mean time, I suppose you can write your own connection pool as the RedisConnection has an Error Event you can use for this, to detect when it's dead.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • The right way to work with BookSleeve is by maintaining one connection, opposite to the way you handle regular DB connection pooling. That's why I asked this question... Please see @MarcGravell's answer [here](http://stackoverflow.com/a/6478883/290343). He says: _"One note: the connection is thread safe and intended to be massively shared)"; don't do a connection per operation."_ – Ofer Zelig Dec 28 '11 at 17:00
  • I got that one, but a RedisConnection does not survive socket errors because it sits on a member socket reference, so you legitimately need some sort of pool mechanism, and it's in the roadmap. – Simon Mourier Dec 28 '11 at 23:56
  • But what to do now that they still don't have that feature? I need (and I believe many others...) a working pattern. And always opening/closing RedisConnection (wrapping it in a 'using'), at the current BookSleeve release, is a lot of overhead, as opposed to ADO.NET connections. – Ofer Zelig Dec 29 '11 at 08:26
2

I'm not a C# programmer, but the way I'd look at the problem is the following:

  1. I'd code a generic function that would take as parameters the redis connection and a lambda expression representing the Redis command

  2. if trying to execute the Redis command would result in an exception pointing out a connectivity issue, I've re-initialize the connection and retry the operation

  3. if no exception is raised just return the result

Here is some sort of pseudo-code:

function execute(redis_con, lambda_func) {
    try {
        return lambda_func(redis_con)
    }
    catch(connection_exception) {
        redis_con = reconnect()
        return  lambda_func(redis_con)
    }
}
Alex Popescu
  • 3,982
  • 18
  • 20
  • A `try..catch` block should indeed be part of the solution, but in the micro level, I believe. The overall interface should be a sort of a singleton that will always serve a valid connection for use. Internally it would check if the "current" connection is alive or try to enlive it (this fragment would be in a try..catch), and if it fails, it will throw an exception. Since almost 2 weeks past since I initially asked the question, I started working on a code myself and I will publish it here. (in the first place I tried to obey to http://en.wikipedia.org/wiki/Reinventing_the_wheel) – Ofer Zelig Jan 07 '12 at 21:52