Node-redis: Fehler, der von retry_strategy zurückgegeben wird, bleibt unabgefangen

Erstellt am 27. Feb. 2017  ·  19Kommentare  ·  Quelle: NodeRedis/node-redis

Ich habe den folgenden Code und versuche, die unterbrochene Verbindung mit iptables -A OUTPUT -p tcp --dport 6379 -j REJECT zu imitieren.

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

Es wird angenommen, dass alle Redis-Fehler im Fehlerereignis ausgegeben werden. Aber hier ist, was ich in der Konsolenausgabe habe:

21:00:14.666Z TRACE-Skript: Redis-Client: Verbinden
21:00:14.695Z TRACE-Skript: Redis-Client: bereit
21:10:23.837Z TRACE-Skript: Redis-Client: Ende
Strategieüberprüfung wiederholen
{ Versuch: 1,
error: { [Fehler: Redis-Verbindung zu redis.callision. info:6379 fehlgeschlagen - ECONNRESET lesen] Code: 'ECONNRESET', Fehler: 'ECONNRESET', Systemaufruf: 'read' },
total_retry_time: 0,
times_connected: 1 }

/node_modules/q/q.js:155
werfen e;
^
AbortError: Streamverbindung beendet und Befehl abgebrochen. Es könnte verarbeitet worden sein.
at RedisClient.flush_and_error (/node_modules/redis/index.js:350:23)
bei RedisClient.connection_gone (/node_modules/redis/index.js:612:18)
bei RedisClient.on_error (/node_modules/redis/index.js:398:10)
am Sockel.(/node_modules/redis/index.js:272:14)
bei emitOne (events.js:90:13)
bei Socket.emit (events.js:182:7)
bei emitErrorNT (net.js:1255:8)
at nextTickCallbackWith2Args (node.js:474:9)
at process._tickCallback (node.js:388:17)

Und als Frage: Warum dauert es etwa 10 Minuten, bis die Verbindung unterbrochen wird? Gibt es eine Möglichkeit, einen Fehler auszulösen, wenn innerhalb von 10 Sekunden keine Reaktion erfolgt? Kann eine beliebige Option wie response_timeout usw. sein.

  • Version : node_redis v.2.6.5 und Redis 3.0.7
  • Plattform : Node.js v5.5.0 auf Ubuntu 14.04.4 LTS
  • Beschreibung : Fehler von retry_strategy bleibt unentdeckt
pending-author-input

Hilfreichster Kommentar

Irgendwelche Neuigkeiten ? Ich habe das gleiche Problem.

Alle 19 Kommentare

@pavelsc Ich habe versucht, dies zu reproduzieren, aber bisher konnte ich es nicht.

Bitte versuchen Sie, das Problem ohne Module von Drittanbietern zu reproduzieren. Derzeit scheinst du zumindest q .

Ich stoße auf den gleichen Fehler. Wenn ich dem Redis-Client absichtlich eine ungültige URL bereitstelle, wird die Methode on.error nicht aufgerufen. Hier ist ein einfaches Beispiel:

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

Im Moment kehre ich zur Verwendung von connect_timeout zurück, das nach Ablauf eines Connect-Timeouts korrekt bei 'error' ausgibt.

Ich habe das gleiche Problem, die Verwendung einer benutzerdefinierten retry_strategy mit einem fehlerhaften Endpunkt endet in "AbortError:"

Das hat mich heute auch erwischt. Wenn man sich den Code kurz ansieht, scheint dies beabsichtigtes Verhalten zu sein. https://github.com/NodeRedis/node_redis/blob/79558c524ff783000a6027fb159739770f98b10e/index.js#L405 gibt explizit an, dass, wenn retry_strategy gesetzt ist, der Fehler nicht ausgegeben und stattdessen weiterhin ausgelöst wird. Ich wäre jedoch gespannt, warum dies der Fall ist, es scheint keinen Grund zu geben, warum es es nicht ausgeben kann, anstatt es aus einem kurzen Blick zu werfen. Gibt es einen Grund, warum diese Bedingung nicht entfernt werden konnte, sodass der Fehler immer ausgegeben wird?

Dieses Problem habe ich auch ständig.

Auch ich kann keine Fehler abfangen, wenn ich ein 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;
}

mit:

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

Beim Debuggen der Anwendung komme ich in die ENOTFOUND Prüfung, wie oben in retry_strategy aber es ruft nicht den Fehlerereignishandler auf.

Ich habe das gleiche Problem, nachdem ich den Quellcode durchforstet habe, habe ich festgestellt, dass, wenn wir uns ändern
diese Zeile (oder Debug-Modus aktivieren)
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L381 -L382

Und fügen Sie diesen Code hier ein (fügen Sie sofort einen Fehler zum Array hinzu)
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L352 -L353

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

Es funktioniert und gibt 'Fehler' korrekt aus.

Die verschachtelte Schleife in dieser Funktion wird nicht ausgeführt, weil 'command_queue' leer ist und Fehler nie zum Array hinzugefügt und daher nicht ausgegeben werden. Wenn ich das richtig verstehe, ist es ein ziemlich alter Code, also brauchen wir Input von den Betreuern oder von @BridgeAR

Ich habe auch gesehen, dass bei der ersten fehlgeschlagenen Verbindung das Ereignis "Ende" ausgegeben wird, was etwas bedeuten könnte (oder nicht). Ich habe Redis vor zwei Tagen abgeholt, bin mir also noch nicht sicher, wie die Interna funktionieren. Ich werde versuchen, ein bisschen weiter zu forken und herumzugraben, wenn / wenn ich Zeit habe.

Und buchstäblich scheint die nächste Ausgabe mit diesem Problem verbunden zu sein #1198

@v1adko Ich bin gerade auf Reisen, aber ich versuche heute oder morgen später einen Blick darauf zu werfen (es sei denn, Ruben schlägt mich).

Ich habe meine Redis-URL absichtlich falsch eingestellt, um Fehlerszenarien zu testen, aber ich sehe, dass meine retry_strategy nicht aufgerufen wird, wenn ich versuche, eine Verbindung zu Redis herzustellen. retry_strategy wird nur aufgerufen, wenn die Verbindung geschlossen wird.

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

`

Könnte mir bitte jemand helfen, dieses Problem zu lösen.

Hier gilt das gleiche. Dieser böse Hack scheint das erwartete Verhalten zu erzeugen, ist sich aber nicht sicher, ob er weitere Auswirkungen hat:

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

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

Ich habe im Moment die gleichen Probleme. Mit retry_strategy wird der Fehler zurückgegeben, wie im Beispiel in der Readme angegeben, aber vom Client wird kein Fehler ausgegeben. Die von @v1adko vorgeschlagenen

Ich frage mich, was die hier erwähnte Rückwärtsinkompatibilität ist?
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L380

Wie von @maael betont , scheint das Verhalten beabsichtigt zu sein, wenn retry_strategy festgelegt ist. Ist das Verhalten also zu erwarten, aber die Dokumentation ist falsch? Sollte ich Fehler für den Client manuell ausgeben, wie von

Bearbeiten: Während ich in das Paket wühle, stelle ich fest, dass das manuelle Ausgeben wahrscheinlich nicht der richtige Weg ist. Scheint, dass ich die erwähnten Breaking Changes verstehen muss.

Irgendwelche Neuigkeiten ? Ich habe das gleiche Problem.

Irgendwelche Neuigkeiten?

ist eine falsche Idee:
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); }

Haben Sie das gleiche Problem, retry_Strategy löst kein Fehlerereignis aus, noch keine Lösung?

Hat jemand Erfolg gehabt?

Wir haben unsere Implementierung stattdessen auf https://github.com/luin/ioredis umgestellt, was einige Verbesserungen mit sich brachte (native Promises, lazyConnect (vermeiden Sie das Öffnen einer Verbindung beim Instanziieren des Redis-Clients, haben uns geholfen, Fehler genau dort zu behandeln, wo wir es brauchten)), und ermöglicht die Ausführung des folgenden Codes:

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

Verwenden Sie die folgenden 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;
};

Und utils/redis.test.js Datei:

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

Umgebungsvariablen:

REDIS_URL=localhost:6379
REDIS_PASSWORD=mypasswordissostrong
War diese Seite hilfreich?
0 / 5 - 0 Bewertungen