Node-redis: retry_strategy no se restablece después de eliminar el error

Creado en 18 feb. 2017  ·  21Comentarios  ·  Fuente: NodeRedis/node-redis

  • Versión : node_redis 2.6.5, redis 3.2.7
  • Plataforma : Node.js 7.5.0 en Mac OS 10.12.3
  • Descripción :

Estoy usando el siguiente código

const redis = require('redis');

const client = redis.createClient({
  url: 'redis://localhost:6379',
  retry_strategy: options => new Error('Fail')
});

setInterval(() => client.incr('some-key', console.log), 5000);

corriendo contra un redis local ( docker run --rm -p 6379:6379 redis )

Después de ver la primera salida, elimino el redis para simular la desconexión. Cuando veo que aparece el error, reinicio el redis. La conexión no vuelve a aparecer. Esperaría que después de descargar el error a los controladores fuera de línea, el cliente intentara volver a conectarse en el siguiente comando. en cambio, permanece en estado cerrado. También regresa con AbortError lugar de new Error('Fail') .

Feature Request

Comentario más útil

Actualización rápida aquí.
Agregué enable_offline_queue = false a mis opciones createClient , y parece haber hecho lo que quería, que es:
"Continuar intentando volver a conectarme a mi servidor Redis según la función retry_strategy , pero arroja errores de inmediato en respuesta a los intentos de usar el cliente mientras tanto. Luego, si puede volver a conectarse, simplemente comience a trabajar de nuevo ".

FYI: Emite el siguiente error en una llamada get : AbortError: SET can't be processed. Stream not writeable.

Para mi uso como una capa recta entre la base de datos y mi aplicación, esto debería estar bien. Sería bueno saber si existe una solución más "robusta" posible donde:

  • El cliente sigue intentando volver a conectarse basándose en retry_strategy
  • Las llamadas / consumidores / usuarios del Cliente no se cuelgan mientras se vuelve a conectar
  • Los comandos aún se pueden poner en cola (¿condicionalmente?) Para ejecutarse si / cuando se restablece una conexión ... en caso de que esté incrementando cosas, etc.

Probablemente muchos escenarios difíciles a considerar aquí ...

Todos 21 comentarios

A través de la documentación de retry_strategy, parece que node_redis solo intentará volver a conectarse si la función devuelve un número.

Si devuelve un número de esta función, el reintento ocurrirá exactamente después de ese tiempo en milisegundos.

En su caso, está devolviendo un no número, que no cumple con los requisitos para intentar volver a conectarse.

Si devuelve un número que no es, no se volverá a intentar y todos los comandos fuera de línea se vacían con errores.

el flujo que estoy buscando es: al desconectar, vuelva a intentar conectarse 3 veces con un intervalo de 1 segundo. Si en la tercera vez todavía está desconectado, debería devolver todos los comandos pendientes con un error. No quiero que deje de intentar volver a conectarse.

Con una versión modificada del ejemplo dado en README.md, debería poder lograr lo que está buscando. Sin embargo, una vez que se hayan agotado los intentos máximos de 3, el cliente no intentará reconectarse automáticamente en el siguiente comando. Creo que necesitaría llamar manualmente createClient() nuevamente.

const client = redis.createClient({
    url: 'redis://localhost:6379',
    retry_strategy: (options) => {
        if (options.times_connected >= 3) {
            // End reconnecting after a specific number of tries and flush all commands with a individual error
            return new Error('Retry attempts exhausted');
        }
        // reconnect after
        return 1000;
    }
});

Sí, dejé de lado los intentos ya que eso no hace ninguna diferencia aquí.

Así que básicamente te refieres a:

let client = createClient();
function createClient () {
  return redis.createClient({
    url: 'redis://localhost:6379',
    retry_strategy: (options) => {
      client = createClient();
      return new Error('Retry attempts exhausted');
    }
  });
}

¿Lo que implica que ya no puedo pasar mi cliente redis en mi código?

Puede crear un contenedor ligero para administrar una instancia única del cliente redis.

// redisClient.js

let redis = require("redis");

let client = null;

const options = {
    url: 'redis://localhost:6379',
    retry_strategy: (options) => {
        client = null;
        return new Error("Redis client connection dropped.");
    }
};

module.exports = {
    getClient: () => {
        if (client == null) {
            client = redis.createClient(options);
        }
        return client;
    }
};

Usar:

// usage

let client = require("redisClient.js");

client.getClient().get("some key");
client.getClient().set("some key", "some value");

Supongo que esto es muy similar a llamar a createClient() en la estrategia de reintento, pero no soy un gran admirador de ese método.

Si desea reducir la necesidad de llamar a getClient() todo el tiempo, puede hacer un contenedor grande que envuelva todas las llamadas de redis como get() , set() , etc., y llame a getClient() en cada uno de esos métodos. Sin embargo, este método de getClient() es un método bastante común de administrar conexiones de manera perezosa en lugar de ansiosamente.

claro, pero entonces ¿por qué esta biblioteca proporciona alguna estrategia de reintento si tiene que envolverla en un contenedor de reintento adicional de todos modos?

No soy el autor, así que no estoy seguro de las motivaciones detrás de la implementación específica de retry_strategy. En mis propios usos de node_redis, he encontrado que la funcionalidad actual de retry_strategy es útil, pero estoy de acuerdo en que carece de la capacidad de reintentar automáticamente sin importar qué. En mis usos, siempre he devuelto un número de retry_strategy y nunca devuelvo un error, ya que siempre quiero que se realice el intento de conexión. Sin embargo, registro los errores desde retry_strategy.

¿Qué tal algo como esto? https://github.com/viglucci/node_redis/tree/feature-reconnect-after-flush#retry_strategy -example

var client = redis.createClient({
    retry_strategy: function (options) {
        if (options.error && options.error.code === 'ECONNREFUSED') {
            // End reconnecting on a specific error and flush all commands with a individual error
            return new Error('The server refused the connection');
        }
        if (options.total_retry_time > 1000 * 60 * 60) {
            // End reconnecting after a specific timeout and flush all commands with a individual error
            return new Error('Retry time exhausted');
        }
        // attempt reconnect after retry_delay, and flush any pending commands
        return {
            retry_delay: Math.min(options.attempt * 100, 3000),
            error: new Error('Your custom error.');
        }
    }
});

Conceptualmente tiene mucho sentido. Otra forma de implementar esto podría ser dividiendo esto en dos estrategias:

var client = redis.createClient({
  // this strategy handles the pending redis commands when the connection goes down
  flush_strategy (options) {
    if (options.attempt >= 3) {
      // flush all pending commands with this error
      return new Error('Redis unavailable')
    }
    // let the connection come up again on its own
    return null;
  },
  // this strategy handles the reconnect of a failing redis
  retry_strategy (options) {
    if (options.total_retry_time > 1000 * 60 * 60) {
      // The connection is never going to get up again
      // kill the client with the error event
      return new Error('Retry time exhausted');
    }
    // attempt reconnect after this delay
    return Math.min(options.attempt * 100, 3000)
  }
});

Realmente no me gusta el nombre flush_strategy pero uno podría pensar en algo mejor.

Oh, interesante. Lo que está sugiriendo aquí se parece más a cambiar el nombre del retry_strategy actual a connection_strategy y usar el retry_strategy como lo que ha denominado flush_strategy .

var client = redis.createClient({
    // this strategy handles reconnection
    connection_strategy (options) {
        if (options.total_retry_time > 1000 * 60 * 60) {
            // The connection is never going to get up again
            // kill the client with the error event
            return new Error('Retry time exhausted');
        }
        // attempt reconnect after this delay
        return Math.min(options.attempt * 100, 3000)
    },
    // this strategy handles the pending redis commands when the connection goes down
    retry_strategy (options) {
        if (options.attempt >= 3) {
            // flush all pending commands with this error
            return new Error('Redis unavailable');
        }
        // let the client attempt the commands once a connection is available
        return null;
    },
});

Implementado: https://github.com/viglucci/node_redis/tree/feature-connection-strategy#retry_strategy -example

@Janpot, ¿ puede elaborar el caso de uso en el que sea necesario? Traté de reducir la cantidad de opciones para que sea más fácil de usar y retry_strategy ya es una opción poderosa.

@viglucci, ¡ gracias por responder aquí y dar buenos comentarios! Personalmente, me gusta su sugerencia de devolver un objeto en caso de que se implemente. Esto no sería realmente un cambio importante, aunque estoy planeando abrir una rama v3 pronto. Actualmente es muy complicado implementar muchas funciones nuevas y eliminar todas las cosas obsoletas ayudará mucho.

@BridgeAR Es más o menos que en este momento, si la conexión de redis se interrumpe por alguna razón, todos mis comandos simplemente se cuelgan. Supongamos que tengo un respaldo para la operación que está haciendo redis, entonces ese respaldo también se bloquea. Prefiero que redis devuelva un error después de algunos reintentos, que puedo detectar y emplear el respaldo. Eso no significa que no quiera que el cliente deje de intentar conectarse. Puede que Redis vuelva a aparecer en un minuto.

Simplemente no veo el sentido del comportamiento actual. Devuelve un error en el controlador de reintentos y luego su cliente muere para siempre. ¿Quién necesita ese tipo de comportamiento a menos que cree un cliente para cada comando?

O puede que no vea el caso de uso para eso, por supuesto 😄

@BridgeAR Gracias. Bueno, si tiene una hoja de ruta "camino a la V3" con tareas simples como eliminar el soporte para los parámetros de configuración / eliminar las advertencias de obsolescencia, etc., le interesará enviar solicitudes de extracción. Probablemente no podría abordar un trabajo grande como agregar soporte para comandos de redis adicionales, pero probablemente podría hacer cosas simples de limpieza de la casa si estuvieran abiertas para PR una vez que esté disponible una rama de función V3.

@viglucci me invité como colaborador. Voy a hacer un plan pronto

+1 para una solución a este problema.

Estoy usando Express, y mientras mi retry_strategy todavía devuelve enteros para intentar conectarse de nuevo en el futuro, los comandos (y las solicitudes web) continúan acumulándose / respaldando en lugar de lanzar algo para que puedan obtener sigan con sus vidas ... y es de esperar que, eventualmente, se restablezca una conexión antes de que la estrategia diga que se rindan o lo que sea.

¿Se puede lograr esto? @Janpot, ¿ ha encontrado algo disponible actualmente para resolver esta situación?

¿Quizás algo con las opciones enable_offline_queue y / o retry_unfulfilled_commands pueda lograr esto?

¡Gracias a todos por su arduo trabajo y ayuda!

Actualización rápida aquí.
Agregué enable_offline_queue = false a mis opciones createClient , y parece haber hecho lo que quería, que es:
"Continuar intentando volver a conectarme a mi servidor Redis según la función retry_strategy , pero arroja errores de inmediato en respuesta a los intentos de usar el cliente mientras tanto. Luego, si puede volver a conectarse, simplemente comience a trabajar de nuevo ".

FYI: Emite el siguiente error en una llamada get : AbortError: SET can't be processed. Stream not writeable.

Para mi uso como una capa recta entre la base de datos y mi aplicación, esto debería estar bien. Sería bueno saber si existe una solución más "robusta" posible donde:

  • El cliente sigue intentando volver a conectarse basándose en retry_strategy
  • Las llamadas / consumidores / usuarios del Cliente no se cuelgan mientras se vuelve a conectar
  • Los comandos aún se pueden poner en cola (¿condicionalmente?) Para ejecutarse si / cuando se restablece una conexión ... en caso de que esté incrementando cosas, etc.

Probablemente muchos escenarios difíciles a considerar aquí ...

@nueva casa ,

Gracias por mencionar esto. Queríamos el mismo comportamiento para nuestra aplicación y su solución funcionó para devolver inmediatamente un error a la persona que llama, pero permítanos volver a intentar conectarnos indefinidamente.

Gracias @newhouse , no estoy seguro de haber encontrado esto alguna vez.

En mi opinión, este debería ser el comportamiento predeterminado, o al menos explícitamente mencionado en los documentos alrededor de retry_strategy . Avíseme si cree que esto último es razonable y abriré un PR para agregar los documentos.

@bwhitty un PR de documentos de cualquier persona sería bienvenido 👍

Un problema con el uso de enable_offline_queue = false es que los comandos enviados justo después de llamar a createClient , pero antes de que se abra la conexión, también fallarán.

Un problema con el uso de enable_offline_queue = false es que los comandos enviados justo después de llamar a createClient , pero antes de que se abra la conexión, también fallarán.

@jkirkwood ¿ Parece que querrá retrasar el intento de enviar sus primeros comandos hasta que se emita el evento ready (o un evento similar apropiado)?

¿Fue útil esta página
0 / 5 - 0 calificaciones