Node-redis: L'erreur, qui est renvoyée par retry_strategy reste non détectée

Créé le 27 févr. 2017  ·  19Commentaires  ·  Source: NodeRedis/node-redis

J'ai le code ci-dessous et j'essaie d'imiter la connexion interrompue en utilisant 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');
});

Il est supposé que toutes les erreurs redis sont émises en cas d'erreur. Mais voici ce que j'ai en sortie de console :

21:00:14.666Z Script TRACE : Client Redis : se connecter
21:00:14.695Z Script TRACE : Client Redis : prêt
21:10:23.837Z Script TRACE : Client Redis : fin
réessayer la vérification de la stratégie
{ tentative : 1,
erreur : { [Erreur : connexion Redis à redis.callision. info:6379 a échoué - read ECONNRESET] code: 'ECONNRESET', errno: 'ECONNRESET', syscall: 'read' },
total_retry_time : 0,
fois_connecté : 1 }

/node_modules/q/q.js:155
lancer e;
^
AbortError : la connexion au flux s'est terminée et la commande a été abandonnée. Il a peut-être été traité.
à RedisClient.flush_and_error (/node_modules/redis/index.js:350:23)
à RedisClient.connection_gone (/node_modules/redis/index.js:612:18)
sur RedisClient.on_error (/node_modules/redis/index.js:398:10)
à Socket.(/node_modules/redis/index.js:272:14)
àemimitOne (events.js:90:13)
à Socket.emit (events.js:182:7)
àémettreErrorNT (net.js:1255:8)
à nextTickCallbackWith2Args (node.js:474:9)
à process._tickCallback (node.js:388:17)

Et comme question : Pourquoi cela prend-il environ 10 minutes pour détecter que la connexion a disparu ? Existe-t-il un moyen de générer une erreur en cas de non-réponse dans les 10 secondes ? Peut être n'importe quelle option comme response_timeout etc.

  • Version : node_redis v.2.6.5 et Redis 3.0.7
  • Plateforme : Node.js v5.5.0 sur Ubuntu 14.04.4 LTS
  • Description : l'erreur de retry_strategy n'est pas détectée
pending-author-input

Commentaire le plus utile

Des nouvelles ? J'ai le même problème.

Tous les 19 commentaires

@pavelsc J'ai essayé de reproduire cela mais jusqu'à présent, je ne pouvais pas.

Veuillez essayer de reproduire le problème sans aucun module tiers. Actuellement, vous semblez utiliser au moins q .

Je rencontre la même erreur. Si je fournis intentionnellement au client Redis une mauvaise URL, la méthode on.error n'est pas invoquée. Voici un exemple simple :

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

Pour l'instant, je reviens à l'utilisation de connect_timeout, qui émet correctement en cas d'erreur après l'expiration d'un délai de connexion.

J'ai le même problème, l'utilisation d'une retry_strategy personnalisée avec un mauvais point de terminaison aboutit à « AbortError : »

Cela m'a surpris aussi aujourd'hui. En regardant brièvement le code, cela semble être un comportement intentionnel. https://github.com/NodeRedis/node_redis/blob/79558c524ff783000a6027fb159739770f98b10e/index.js#L405 indique explicitement que si retry_strategy est défini, ne pas émettre l'erreur et continuer à la lancer. Je serais curieux de savoir pourquoi c'est le cas, il ne semble pas y avoir de raison pour laquelle il ne peut pas l'émettre au lieu de le jeter d'un coup d'œil. Y a-t-il une raison pour laquelle ce conditionnel n'a pas pu être supprimé, de sorte que l'erreur soit toujours émise ?

J'ai aussi ce problème, régulièrement.

Je ne suis pas non plus en mesure de détecter les erreurs lors de la réception d'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;
}

avec:

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

En déboguant l'application, j'entre dans le contrôle ENOTFOUND comme indiqué ci-dessus dans le retry_strategy mais il n'appelle pas le gestionnaire d'événements d'erreur.

J'ai le même problème, après avoir fouillé dans le code source, j'ai trouvé que si nous modifions
cette ligne (ou activer le mode débogage)
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L381 -L382

Et insérez ce morceau de code ici (ajoutez immédiatement l'erreur au tableau)
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L352 -L353

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

Il fonctionne et émet une 'erreur' correctement.

La boucle imbriquée dans cette fonction n'est pas exécutée, car 'command_queue' est vide et l'erreur n'est jamais ajoutée au tableau et n'est donc pas émise. Si je comprends bien, c'est un morceau de code assez ancien, nous avons donc besoin de la contribution des responsables ou de @BridgeAR

J'ai également vu que lors de la première connexion défaillante, l'événement 'end' était émis, cela pourrait signifier quelque chose (ou non), j'ai récupéré Redis il y a deux jours, donc je ne sais pas encore comment fonctionnent les composants internes. J'essaierai de creuser un peu plus quand j'aurai le temps.

Et littéralement, le prochain problème semble lié à ce problème #1198

@v1adko Je voyage actuellement, mais j'essaierai d'y jeter un œil plus tard aujourd'hui ou demain (à moins que Ruben ne me devance).

Mon URL de redis est délibérément erronée pour tester les scénarios d'erreur, mais je vois que mon retry_strategy n'est pas invoqué lorsque j'essaie de me connecter à redis. retry_strategy est invoqué uniquement lorsque la connexion est fermée.

`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 qualifierUrl = (url) => {
return '//' + url.replace(/^\/+/,"");
} ;

`

Quelqu'un pourrait-il s'il vous plaît m'aider à résoudre ce problème.

Pareil ici. Ce méchant hack semble créer le comportement attendu, mais je ne sais pas s'il a des implications plus larges :

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

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

J'ai les mêmes problèmes en ce moment. En utilisant retry_strategy, renvoyant l'erreur comme indiqué par l'exemple dans le fichier Lisez-moi, mais aucune erreur n'est émise par le client. Les correctifs proposés par @v1adko résolvent le problème au moins à sa valeur nominale.

Je me demande quelle est l'incompatibilité ascendante mentionnée ici?
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L380

Comme l'a souligné @maael , le comportement semble être intentionnel lorsque retry_strategy est défini. Le comportement est-il donc attendu, mais la documentation est incorrecte ? Dois-je émettre manuellement des erreurs pour le client comme suggéré par @c24w ?

edit : Alors que je creuse dans le package, je me rends compte qu'émettre manuellement n'est probablement pas la voie à suivre. Il semble que j'ai besoin de comprendre les changements de rupture mentionnés.

Des nouvelles ? J'ai le même problème.

Des nouvelles?

est une mauvaise idée de faire:
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); }

ayant le même problème, retry_Strategy ne déclenche pas d'événement d'erreur, pas encore de solution ?

Quelqu'un a-t-il réussi ?

Nous avons plutôt basculé notre implémentation sur https://github.com/luin/ioredis , ce qui a apporté quelques améliorations (promises natives, lazyConnect (éviter d'ouvrir une connexion lors de l'instanciation du client redis, nous a aidés à gérer les erreurs exactement là où nous en avions besoin)), et autorise l'exécution du code suivant :

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

En utilisant les 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;
};

Et le fichier utils/redis.test.js :

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 d'environnement :

REDIS_URL=localhost:6379
REDIS_PASSWORD=mypasswordissostrong
Cette page vous a été utile?
0 / 5 - 0 notes