Node-redis: Erro, que é retornado de retry_strategy permanece não detectado

Criado em 27 fev. 2017  ·  19Comentários  ·  Fonte: NodeRedis/node-redis

Eu tenho o código abaixo e tento imitar a conexão perdida 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');
});

Supõe-se que todos os erros do redis são emitidos no evento de erro. Mas aqui está o que eu tenho na saída do console:

21: 00: 14.666Z Script TRACE: cliente Redis: conectar
21: 00: 14.695Z Script TRACE: cliente Redis: pronto
21: 10: 23.837Z Script TRACE: cliente Redis: fim
repetir verificação de estratégia
{tentativa: 1,
erro: {[Erro: conexão do Redis com redis.callision. info: 6379 falhou - ler ECONNRESET] código: 'ECONNRESET', errno: 'ECONNRESET', syscall: 'ler'},
total_retry_time: 0,
times_connected: 1}

/node_modules/q/q.js:155
jogue e;
^
AbortError: Conexão de fluxo encerrada e comando abortado. Pode ter sido processado.
em RedisClient.flush_and_error (/node_modules/redis/index.js:350:23)
em RedisClient.connection_gone (/node_modules/redis/index.js:612:18)
em RedisClient.on_error (/node_modules/redis/index.js:398:10)
em Socket.(/node_modules/redis/index.js:272:14)
em emitOne (events.js: 90: 13)
em Socket.emit (events.js: 182: 7)
em emitErrorNT (net.js: 1255: 8)
em nextTickCallbackWith2Args (node.js: 474: 9)
em process._tickCallback (node.js: 388: 17)

E como uma pergunta: por que leva cerca de 10 minutos para detectar que a conexão acabou? Existe alguma maneira de gerar um erro caso não haja resposta em 10 segundos? Pode ser qualquer opção como response_timeout etc.

  • Versão : node_redis v.2.6.5 e Redis 3.0.7
  • Plataforma : Node.js v5.5.0 no Ubuntu 14.04.4 LTS
  • Descrição : o erro de retry_strategy permanece não detectado
pending-author-input

Comentários muito úteis

Alguma novidade ? Eu tenho o mesmo problema.

Todos 19 comentários

@pavelsc Eu tentei reproduzir isso, mas até agora não consegui.

Tente reproduzir o problema sem módulos de terceiros. Atualmente você parece usar pelo menos q .

Estou encontrando o mesmo erro. Se eu fornecer intencionalmente ao cliente Redis um URL inválido, o método on.error não será invocado. Aqui está um exemplo simples:

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 enquanto, estou voltando a usar o connect_timeout, que emite um 'erro' corretamente depois que o tempo limite da conexão expira.

Estou tendo o mesmo problema, usar um retry_strategy personalizado com um endpoint inválido acaba em "AbortError:"

Isso me pegou hoje também. Olhando o código brevemente, esse parece ser um comportamento intencional. https://github.com/NodeRedis/node_redis/blob/79558c524ff783000a6027fb159739770f98b10e/index.js#L405 afirma explicitamente que se retry_strategy estiver definido, não emitirá o erro e, em vez disso, continuará a lançá-lo. Eu ficaria curioso para saber por que esse é o caso, porém, não parece haver uma razão pela qual ele não pode emiti-lo em vez de lançar de uma olhada rápida. Existe alguma razão para que esta condicional não possa ser retirada, para que o erro seja sempre emitido?

Eu também estou tendo esse problema, de forma consistente.

Eu também não consigo detectar erros ao receber 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;
}

com:

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

Depurando o aplicativo, estou entrando na verificação ENOTFOUND conforme observado acima em retry_strategy mas não estou chamando o manipulador de eventos de erro.

Eu tenho o mesmo problema, depois de vasculhar o código-fonte, descobri que se mudarmos
esta linha (ou habilite o modo de depuração)
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L381 -L382

E insira este pedaço de código aqui (adicione o erro ao array imediatamente)
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L352 -L353

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

Ele funciona e emite 'erro' corretamente.

O loop aninhado nessa função não é executado, porque 'command_queue' está vazio e o erro nunca é adicionado ao array e, portanto, não é emitido. Se bem entendi, é um código bem antigo, então precisamos da entrada dos mantenedores ou de @BridgeAR

Também vi que na primeira falha de conexão emite o evento 'final', isso pode significar alguma coisa (ou não), peguei o Redis há dois dias, então não tenho certeza de como os internos funcionam ainda. Vou tentar mexer e cavar um pouco mais quando se / quando tiver tempo.

E, literalmente, a próxima edição parece estar conectada a este problema # 1198

@ v1adko No momento estou viajando, mas tentarei dar uma olhada nisso mais tarde hoje ou amanhã (a menos que Ruben me

Eu tenho meu url do redis deliberadamente errado para testar cenários de erro, mas vejo que meu retry_strategy não foi invocado ao tentar me conectar ao redis. retry_strategy é invocado apenas quando a conexão é fechada.

`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 (/ ^ \ / + /, "");
};

`

Alguém poderia me ajudar a resolver esse problema.

Mesmo aqui. Este hack desagradável parece criar o comportamento esperado, mas não tenho certeza se tem implicações mais amplas:

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

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

Estou tendo os mesmos problemas no momento. Usando retry_strategy, retornando o erro conforme indicado pelo exemplo no Leiame, mas nenhum erro é emitido pelo cliente. As correções propostas por @ v1adko resolvem o problema pelo menos pelo valor de face.

Estou me perguntando qual é a incompatibilidade com versões anteriores mencionada aqui.
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L380

Conforme apontado por @maael , o comportamento parece ser intencional quando retry_strategy é definido. Então, o comportamento é esperado, mas a documentação está incorreta? Devo emitir erros para o cliente manualmente, conforme sugerido por @ c24w ?

editar: Conforme estou cavando no pacote, estou percebendo que emitir manualmente provavelmente não é o caminho a seguir. Parece que preciso entender as alterações significativas mencionadas.

Alguma novidade ? Eu tenho o mesmo problema.

Alguma novidade?

é uma ideia errada de se fazer:
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); }

tendo o mesmo problema, retry_Strategy não disparando evento de erro, nenhuma correção ainda?

Alguém conseguiu?

Em vez disso, mudamos nossa implementação para https://github.com/luin/ioredis , que trouxe algumas melhorias (Promessas nativas, lazyConnect (evite abrir uma conexão ao instanciar o cliente redis, nos ajudou a lidar com os erros exatamente onde precisávamos)), e permite que o seguinte código seja executado:

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 o seguinte 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;
};

E utils/redis.test.js file:

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();
    });
  });
});

Variáveis ​​Env:

REDIS_URL=localhost:6379
REDIS_PASSWORD=mypasswordissostrong
Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

Alchemystic picture Alchemystic  ·  6Comentários

abhaygarg picture abhaygarg  ·  5Comentários

twappworld picture twappworld  ·  7Comentários

michaelwittig picture michaelwittig  ·  3Comentários

Mickael-van-der-Beek picture Mickael-van-der-Beek  ·  6Comentários