2011-12-27 22 views
26

¿Alguien tiene un patrón sólido obteniendo Redis a través de la biblioteca BookSleeve?Mantenimiento de una conexión Redis abierta con BookSleeve

quiero decir:

autor de BookSleeve @MarcGravell recommends no abrir & cerrar la conexión cada vez, sino que mantienen una conexión en toda la aplicación. Pero, ¿cómo puedes manejar los cortes de red? es decir, la conexión podría abrirse con éxito en primer lugar, pero cuando algún código intente leer/escribir en Redis, existe la posibilidad de que la conexión haya caído y usted deba volver a abrirla (y fallar con gracia si no se abre, pero eso es según sus necesidades de diseño.)

Busco fragmento (s) de código que cubra la apertura general de la conexión Redis, y una verificación general activa (+ opcional si no está vivo) que se usaría antes de cada lectura /escribir.

This question sugiere una actitud agradable al problema, pero es solo parcial (no recupera una conexión perdida, por ejemplo), y el accepted answer a esa pregunta dibuja el camino correcto pero no muestra un código concreto.

Espero que este hilo obtenga respuestas sólidas y eventualmente se convierta en una especie de Wiki con respecto al uso de BookSleeve en aplicaciones .Net.

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

ACTUALIZACIÓN IMPORTANTE (21/3/2014):

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

Marc Gravell (@MarcGravell)/Cambio de la pila tiene recently released la biblioteca StackExchange.Redis que finalmente reemplaza a Booksleeve. Esta nueva biblioteca, entre otras cosas, maneja internamente las reconexiones y hace que mi pregunta sea redundante (es decir, no es redundante para Booksleeve ni para mi respuesta a continuación, pero creo que la mejor manera de seguir adelante es comenzar a utilizar la nueva biblioteca StackExchange.Redis).

+0

Utilice un hilo latido con punto alphazero

+0

Sí, necesito la lógica de reconexión de hecho ... en un código robusto y limpio ... Por cierto, buen video de youtube. La primera vez que encuentro esto en SO. –

+1

Brillante: la actualización de la respuesta se produce el mismo día que vemos el problema por primera vez. – Anthony

Respuesta

29

Ya que no he conseguido ningún buenas respuestas, me ocurrió con esta solución (Por cierto, gracias @ Simon y @Alex por tus respuestas!).

Quiero compartirlo con toda la comunidad como referencia. Por supuesto, cualquier corrección será muy apreciada.

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; 
      } 
     } 
    } 
} 
+0

Tu código está haciendo el bloqueo doble, que está [realmente roto] (http: // www. cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html) (al menos en la plataforma Java, pero dudo que el entorno C# sea radicalmente diferente) –

+0

@AlexPopescu, .Net CLR resuelve problemas relacionados con el uso _Double-Check Locking_ que son comunes en otros entornos. Por favor vea: http://msdn.microsoft.com/en-us/library/ff650316.aspx. Pero gracias a su comentario, noté que debería agregar la palabra clave _volatile_ a __instance_. ¡Gracias! –

+0

Estaba probando su solución y, en promedio, es 120 ms más lenta que mi error de devolver una nueva conexión cada vez. http://pastie.org/3965493. ¿Estás usando este código hoy o has hecho alguna modificación en él? –

2

Con otros sistemas (como ADO.NET), esto se logra con un grupo de conexiones. En realidad, nunca obtienes un nuevo objeto Connection, sino que obtienes uno del grupo.

El grupo en sí administra las conexiones nuevas y las conexiones muertas, independientemente del código de la persona que llama. La idea aquí es tener un mejor rendimiento (establecer una nueva conexión es costoso), y sobrevivir a los problemas de red (el código de la persona que llama fallará mientras el servidor está caído pero se reanudará cuando vuelva a estar en línea). De hecho, hay un grupo por AppDomain, por "tipo" de conexión.

Este comportamiento se produce cuando observa cadenas de conexión ADO.NET. Por ejemplo, la cadena de conexión de SQL Server (ConnectionString Property) tiene la noción de 'Pooling', 'Max Pool Size', 'Min Pool Size', etc. Este es también un método ClearAllPools que se usa para reiniciar programáticamente los pools de AppDomain actuales si es necesario. ejemplo.

No veo nada parecido a este tipo de característica buscando el código de BookSleeve, pero parece estar planificado para la próxima versión: BookSleeve RoadMap.

Mientras tanto, supongo que puede escribir su propio grupo de conexiones ya que RedisConnection tiene un evento de error que puede usar para esto, para detectar cuándo está muerto.

+0

La forma correcta de trabajar con BookSleeve es mantener una conexión, al contrario de la forma en que maneja la agrupación de conexiones de base de datos. Es por eso que hice esta pregunta ... Consulte la respuesta de @ MarcGravell [aquí] (http://stackoverflow.com/a/6478883/290343). Él dice: _ "Una nota: la conexión es segura para hilos y está destinada a ser compartida masivamente"; no haga una conexión por operación. "_ –

+0

Obtuve ese, pero un RedisConnection no sobrevive a los errores de socket porque se encuentra en una referencia de socket miembro, por lo que legítimamente necesita algún tipo de mecanismo de grupo, y está en la hoja de ruta. –

+0

¿Pero qué hacer ahora que todavía no tienen esa característica? Necesito (y creo que muchos otros ...) un patrón de trabajo. Y siempre abro/cerrando RedisConnection (envolviéndolo en un 'uso'), en el versión actual de BookSleeve, es una gran sobrecarga, a diferencia de las conexiones ADO.NET. –

2

No soy un programador de C#, pero la forma en que me miro el problema es el siguiente:

  1. me gustaría código de una función genérica que tomaría como parámetros de la conexión y una Redis expresión lambda que representa el comando Redis

  2. si se intenta ejecutar el comando Redis daría lugar a una excepción señalando un problema de conectividad, he reinicializar la conexión y vuelva a intentar la operación

  3. si no hay exce ption se eleva simplemente devolver el resultado

Aquí es una especie de pseudo-código:

function execute(redis_con, lambda_func) { 
    try { 
     return lambda_func(redis_con) 
    } 
    catch(connection_exception) { 
     redis_con = reconnect() 
     return lambda_func(redis_con) 
    } 
} 
+0

Un bloque 'try..catch' debería ser parte de la solución, pero en el nivel micro, creo. La interfaz general debería ser una una especie de singleton que siempre servirá para una conexión válida para su uso. Internamente verificaría si la conexión "actual" está activa o intentará activarla (este fragmento estaría en una try..catch), y si falla, lanzará una excepción. Sinc Hace casi dos semanas desde que inicialmente hice la pregunta, comencé a trabajar en un código y lo publicaré aquí. (en primer lugar, traté de obedecer a http://en.wikipedia.org/wiki/Reinventing_the_wheel) –

Cuestiones relacionadas