Node-redis: El error, que se devuelve desde retry_strategy permanece sin detectar

Creado en 27 feb. 2017  ·  19Comentarios  ·  Fuente: NodeRedis/node-redis

Tengo el código a continuación e intento imitar la conexión interrumpida usando iptables -A OUTPUT -p tcp --dport 6379 -j REJECT .

self.client = redis.createClient(self.cfg.port, self.cfg.host, {
    retry_strategy: function (options) {
        console.log('retry strategy check');
        console.log(options);
        if (options.error) {
            if (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.error.code === 'ECONNRESET') {
                return new Error('The server reset the connection');
            }
            if (options.error.code === 'ETIMEDOUT') {
                return new Error('The server timeouted 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');
        }
        if (options.attempt > 5) {
            // End reconnecting with built in error
            return new Error('Retry attempts ended');
        }
        // reconnect after
        return 1000;
    }
});
self.client.on('ready', function () {
    log.trace('Redis client: ready');
});
self.client.on('connect', function () {
    log.trace('Redis client: connect');
});
self.client.on('reconnecting', function () {
    log.trace('Redis client: reconnecting');
});
self.client.on('error', function (err) {
    log.error({err: err}, 'Listener.redis.client error: %s', err);
    process.exit(1);
});
self.client.on('end', function () {
    log.trace('Redis client: end');
});
self.client.on('warning', function () {
    log.trace('Redis client: warning');
});

Se supone que todos los errores de redis se emiten en un evento de error. Pero esto es lo que tengo en la salida de la consola:

21: 00: 14.666Z Secuencia de comandos TRACE: Cliente Redis: conectar
21: 00: 14.695Z Secuencia de comandos TRACE: Cliente Redis: listo
21: 10: 23.837Z Secuencia de comandos TRACE: cliente Redis: fin
reintentar la verificación de la estrategia
{intento: 1,
error: {[Error: conexión de Redis a redis.callision. info: 6379 falló - leer ECONNRESET] código: 'ECONNRESET', errno: 'ECONNRESET', syscall: 'read'},
total_retry_time: 0,
times_connected: 1}

/node_modules/q/q.js:155
tirar e;
^
AbortError: la conexión de transmisión finalizó y el comando se canceló. Podría haber sido procesado.
en RedisClient.flush_and_error (/node_modules/redis/index.js:350:23)
en RedisClient.connection_gone (/node_modules/redis/index.js:612:18)
en RedisClient.on_error (/node_modules/redis/index.js:398:10)
en Socket.(/node_modules/redis/index.js:272:14)
en emitOne (events.js: 90: 13)
en Socket.emit (events.js: 182: 7)
en emitErrorNT (net.js: 1255: 8)
en nextTickCallbackWith2Args (node.js: 474: 9)
en process._tickCallback (node.js: 388: 17)

Y como pregunta: ¿Por qué se tardan unos 10 minutos en detectar que la conexión se ha ido? ¿Hay alguna forma de generar un error en caso de que no haya respuesta en 10 segundos? Puede ser cualquier opción como response_timeout, etc.

  • Versión : node_redis v.2.6.5 y Redis 3.0.7
  • Plataforma : Node.js v5.5.0 en Ubuntu 14.04.4 LTS
  • Descripción : el error de retry_strategy no se detecta
pending-author-input

Comentario más útil

Hay noticias ? Tengo el mismo problema.

Todos 19 comentarios

@pavelsc Traté de reproducir esto pero hasta ahora no pude.

Intente reproducir el problema sin módulos de terceros. Actualmente parece que al menos usa q .

Me encuentro con el mismo error. Si proporciono intencionalmente al cliente de Redis una URL incorrecta, no se invoca el método on.error. A continuación, se muestra un ejemplo sencillo:

var redis = require("redis");

exports.handler = function (event, context, callback) {

    console.log("Executing test lambda for diagnosing redis issues");

    var redisInfo = {
        HOST: process.env.REDIS_HOST,
        PORT: process.env.REDIS_PORT
    };

    console.log(process.env.REDIS_HOST);
    console.log(process.env.REDIS_PORT);

    console.log("Connecting to Redis...");

    var client = redis.createClient({
        host: redisInfo.HOST,
        port: redisInfo.PORT,
        retry_strategy: function (options) {

            if (options.total_retry_time > 2000) {
                console.log("throwing an error...");
                return new Error('Retry time exhausted');
            }

            return 200;
        }
    });

    // if you'd like to select database 3, instead of 0 (default), call
    // client.select(3, function() { /* ... */ });

    client.on("error", function (err) {
        console.log("Error " + err);
        callback(null, "Error with Redis");
    });

    client.on('connect', function() {
        console.log("Connected to Redis");
    });

    client.on('end', function() {
        console.log("Redis end");
    });

    client.set("string key", "string val", redis.print);
    client.hset("hash key", "hashtest 1", "some value", redis.print);
    client.hset(["hash key", "hashtest 2", "some other value"], redis.print);
    client.hkeys("hash key", function (err, replies) {
        console.log(replies.length + " replies:");
        replies.forEach(function (reply, i) {
            console.log("    " + i + ": " + reply);
        });
        client.quit();
    });

    client.quit();

    callback(null, "Success");
};

Por ahora, estoy volviendo a usar connect_timeout, que emite un 'error' correctamente después de que expira el tiempo de espera de la conexión.

Tengo el mismo problema, el uso de una retry_strategy personalizada con un punto final incorrecto termina en "AbortError:"

Esto también me sorprendió hoy. Al mirar el código brevemente, esto parece ser un comportamiento intencional. https://github.com/NodeRedis/node_redis/blob/79558c524ff783000a6027fb159739770f98b10e/index.js#L405 declara explícitamente que si retry_strategy está configurado, no se emitirá el error y, en su lugar, continuará lanzándolo. Sin embargo, tendría curiosidad por saber por qué este es el caso, no parece haber una razón por la que no pueda emitirlo en lugar de lanzarlo con una mirada rápida. ¿Hay alguna razón por la que este condicional no se pueda eliminar, de modo que siempre se emita el error?

Yo también tengo este problema, constantemente.

Yo tampoco puedo detectar errores al recibir un ENOTFOUND .

{
    host: "foo",
    retry_strategy: function (options) {
        if (options.error && options.error.code === "ENOTFOUND") {
            return new Error("The server was not found");
        }

        // reconnect after
        return 1000;
}

con:

redis.on("error", err => {
    console.error("Cache Error: " + err);
});

Al depurar la aplicación, estoy ingresando a la verificación ENOTFOUND como se indicó anteriormente en retry_strategy pero no invoca el controlador de eventos de error.

Tengo el mismo problema, después de buscar en el código fuente encontré que si cambiamos
esta línea (o habilitar el modo de depuración)
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L381 -L382

E inserte este fragmento de código aquí (agregue el error a la matriz de inmediato)
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L352 -L353

    if (options.error) {
      aggregated_errors.push(options.error);
    }

Funciona y emite 'error' correctamente.

El bucle anidado en esa función no se ejecuta, porque 'command_queue' está vacío y el error nunca se agrega a la matriz y, por lo tanto, no se emite. Si lo entiendo correctamente, es un fragmento de código bastante antiguo, por lo que necesitamos información de los mantenedores o de @BridgeAR

También vi que en la primera conexión fallida emite un evento 'final', que podría significar algo (o no), recogí Redis hace dos días, por lo que aún no estoy seguro de cómo funcionan los componentes internos. Intentaré bifurcar y excavar un poco más cuando tenga tiempo.

Y, literalmente, el siguiente problema parece estar relacionado con este problema # 1198

@ v1adko Actualmente estoy viajando, pero intentaré echarle un vistazo más tarde hoy o mañana (a menos que Ruben me

Tengo mi URL de redis deliberadamente incorrecta para probar escenarios de error, pero veo que mi retry_strategy no se invoca cuando intento conectarme a redis. retry_strategy se invoca solo cuando se cierra la conexión.

`const redis = require ('redis');
const log = require ('./ logUtil'). logger;

module.exports.connect = () => {

var redisRetryStrategy = function(options) {
    if (options.error && options.error.code === 'ECONNREFUSED') {
        // End reconnecting on a specific error and flush all commands with 
        // a individual error
        log.error('The redis server refused the connection');
        return new Error('The redis server refused the connection');
    }

    log.info(`Already spent ${options.total_retry_time} milliseconds to re-establish connection with redis`);
    if (options.total_retry_time > 2000) {
        // End reconnecting after a specific timeout and flush all commands 
        // with a individual error 
        log.error('Retry time exhausted');
        return new Error('Retry time exhausted');
    }
    log.info(`Attempting ${options.attempt} time to establish connection with redis`);
    if (options.attempt > 5) {
        // End reconnecting with built in error 
        log.error('Exhausted the retry attempts to establish connection to redis');
        return undefined;
    }
    // reconnect after 
    return 100;
}


log.info(`Redis connection url is :${process.env.REDIS_URL}`);
var redisClient = redis.createClient(qualifyUrl(process.env.REDIS_URL), {
    retry_strategy: redisRetryStrategy
});

redisClient.offline_queue_length = 3;

redisClient.on('connect', function() {
    console.log('Connected to Redis');
});

redisClient.on('reconnecting', function() {
    console.log('Re-Connecting to Redis');
});

redisClient.on('error', (err)=> {
    console.log(`Error trying to create redis connection: ${JSON.stringify(err)}`);
});
return redisClient;

}

const qualifyUrl = (url) => {
return '//' + url.replace (/ ^ \ / + /, "");
};

'

¿Podría alguien ayudarme a resolver este problema?

Aquí igual. Este truco desagradable parece crear el comportamiento esperado, pero no estoy seguro si tiene implicaciones más amplias:

const client = redis.createClient({
  retry_strategy: ({error}) => client.emit('error', error)
});

client.on('error', console.error);

Tengo los mismos problemas en este momento. Usando retry_strategy, devuelve el error como se indica en el ejemplo en el archivo Léame, pero el cliente no emite ningún error. Las correcciones propuestas por @ v1adko resuelven el problema al menos en su valor nominal.

Me pregunto cuál es la incompatibilidad hacia atrás mencionada aquí.
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L380

Como señaló @maael , el comportamiento parece ser intencional cuando se establece retry_strategy. Entonces, ¿se espera el comportamiento, pero la documentación es incorrecta? ¿Debería emitir errores para el cliente manualmente como lo sugiere @ c24w ?

editar: Mientras profundizo en el paquete, me doy cuenta de que emitir manualmente probablemente no sea el camino a seguir. Parece que necesito comprender los cambios importantes mencionados.

Hay noticias ? Tengo el mismo problema.

¿Hay noticias?

es una idea incorrecta de hacer:
js if (options.error && options.error.code === 'ECONNREFUSED') { // End reconnecting on a specific error and flush all commands with // a individual error return Math.min(options.attempt * 100, 3000); }

teniendo el mismo problema, retry_Strategy no activa el evento de error, ¿todavía no hay solución?

¿Alguien tuvo éxito?

En su lugar, cambiamos nuestra implementación a https://github.com/luin/ioredis , lo que trajo algunas mejoras (promesas nativas, lazyConnect (evitar abrir una conexión al instanciar el cliente de redis, nos ayudó a manejar los errores exactamente donde los necesitábamos)), y permite que se ejecute el siguiente código:

let cachedItem;

  try {
    logger.debug(`Fetching GraphCMS query in redis cache...`);
    // XXX If fetching data from redis fails, we will fall back to running the query against GraphCMS API in order to ensure the client gets the data anyway
    cachedItem = await redisClient.get(body);
  } catch (e) {
    logger.debug(`An exception occurred while fetching redis cache.`);
    logger.error(e);
    epsagon.setError(e);
  }

Usando lo siguiente utils/redis.js :

import { createLogger } from '@unly/utils-simple-logger';
import Redis from 'ioredis';
import epsagon from './epsagon';

const logger = createLogger({
  label: 'Redis client',
});

/**
 * Creates a redis client
 *
 * <strong i="11">@param</strong> url Url of the redis client, must contain the port number and be of the form "localhost:6379"
 * <strong i="12">@param</strong> password Password of the redis client
 * <strong i="13">@param</strong> maxRetriesPerRequest By default, all pending commands will be flushed with an error every 20 retry attempts.
 *          That makes sure commands won't wait forever when the connection is down.
 *          Set to null to disable this behavior, and every command will wait forever until the connection is alive again.
 * <strong i="14">@return</strong> {Redis}
 */
export const getClient = (url = process.env.REDIS_URL, password = process.env.REDIS_PASSWORD, maxRetriesPerRequest = 20) => {
  const client = new Redis(`redis://${url}`, {
    password,
    showFriendlyErrorStack: true, // See https://github.com/luin/ioredis#error-handling
    lazyConnect: true, // XXX Don't attempt to connect when initializing the client, in order to properly handle connection failure on a use-case basis
    maxRetriesPerRequest,
  });

  client.on('connect', function () {
    logger.info('Connected to redis instance');
  });

  client.on('ready', function () {
    logger.info('Redis instance is ready (data loaded from disk)');
  });

  // Handles redis connection temporarily going down without app crashing
  // If an error is handled here, then redis will attempt to retry the request based on maxRetriesPerRequest
  client.on('error', function (e) {
    logger.error(`Error connecting to redis: "${e}"`);
    epsagon.setError(e);
  });

  return client;
};

Y utils/redis.test.js archivo:

import { getClient } from './redis';

let redisClient;
let redisClientFailure;

describe('utils/redis.js', () => {
  beforeAll(() => {
    redisClient = getClient();
    redisClientFailure = getClient('localhost:5555', null, 0); // XXX This shouldn't throw an error because we're using lazyConnect:true which doesn't automatically connect to redis
  });

  afterAll(async () => {
    await redisClient.quit();
    await redisClientFailure.quit();
  });

  describe('should successfully init the redis client', () => {
    test('when provided connection info are correct', async () => {
      // Environment variables are from the .env.test file - This tests a localhost connection only
      expect(redisClient.options.host).toEqual(process.env.REDIS_URL.split(':')[0]);
      expect(redisClient.options.port).toEqual(parseInt(process.env.REDIS_URL.split(':')[1], 10));
      expect(redisClient.options.password).toEqual(process.env.REDIS_PASSWORD);
    });

    test('when connection info are incorrect', async () => {
      expect(redisClientFailure.options.host).toEqual('localhost');
      expect(redisClientFailure.options.port).toEqual(5555);
    });
  });

  describe('should successfully perform native operations (read/write/delete/update)', () => {
    test('when using async/await (using native node.js promises)', async () => {
      const setResult = await redisClient.set('key-1', 'value-1');
      expect(setResult).toEqual('OK');

      const result = await redisClient.get('key-1');
      expect(result).toEqual('value-1');

      const delResult = await redisClient.del('key-1');
      expect(delResult).toEqual(1);

      const setResultB = await redisClient.set('key-1', 'value-1b');
      expect(setResultB).toEqual('OK');

      const resultB = await redisClient.get('key-1');
      expect(resultB).toEqual('value-1b');

      const setResultC = await redisClient.set('key-1', 'value-1c');
      expect(setResultC).toEqual('OK');

      const resultC = await redisClient.get('key-1');
      expect(resultC).toEqual('value-1c');
    });
  });

  describe('should allow to catch an error when failing to open a connection to redis, in order to gracefully handle the error instead of crashing the app', () => {
    test('when connection info are incorrect', async () => {
      expect(redisClientFailure.options.host).toEqual('localhost');
      expect(redisClientFailure.options.port).toEqual(5555);

      try {
        await redisClientFailure.set('key-1', 'value-1'); // This should throw an error, because the connection to redis will be made when executing the
        expect(true).toBe(false); // This shouldn't be called, or the test will fail
      } catch (e) {
        expect(e).toBeDefined();
        expect(e.message).toContain('Reached the max retries per request limit');
      }
      await redisClientFailure.quit();
    });
  });
});

Variables de entorno:

REDIS_URL=localhost:6379
REDIS_PASSWORD=mypasswordissostrong
¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

id0Sch picture id0Sch  ·  4Comentarios

strumwolf picture strumwolf  ·  4Comentarios

shmendo picture shmendo  ·  6Comentarios

Atala picture Atala  ·  3Comentarios

aletorrado picture aletorrado  ·  6Comentarios