Node-redis: Ошибка, возвращаемая retry_strategy, остается неперехваченной

Созданный на 27 февр. 2017  ·  19Комментарии  ·  Источник: NodeRedis/node-redis

У меня есть приведенный ниже код, и я пытаюсь имитировать разорванное соединение, используя 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');
});

Предполагается, что все ошибки Redis возникают в событии ошибки. Но вот что у меня в выводе консоли:

21: 00: 14.666Z Сценарий TRACE: Клиент Redis: подключение
21: 00: 14.695Z Скрипт TRACE: Клиент Redis: готов
21: 10: 23.837Z Сценарий TRACE: клиент Redis: конец
повторная проверка стратегии
{попытка: 1,
ошибка: {[Ошибка: подключение Redis к redis.callision. info: 6379 failed - read ECONNRESET] код: 'ECONNRESET', номер ошибки: 'ECONNRESET', syscall: 'read'},
total_retry_time: 0,
times_connected: 1}

/node_modules/q/q.js:155
бросить е;
^
AbortError: соединение потока завершено и команда прервана. Это могло быть обработано.
в RedisClient.flush_and_error (/node_modules/redis/index.js:350:23)
в RedisClient.connection_gone (/node_modules/redis/index.js:612:18)
в RedisClient.on_error (/node_modules/redis/index.js:398:10)
в Socket.(/node_modules/redis/index.js:272:14)
в emitOne (events.js: 90: 13)
в Socket.emit (events.js: 182: 7)
в emitErrorNT (net.js: 1255: 8)
в nextTickCallbackWith2Args (node.js: 474: 9)
в process._tickCallback (node.js: 388: 17)

И в качестве вопроса: почему обнаружение разрыва связи занимает около 10 минут? Есть ли способ вызвать ошибку в случае отсутствия ответа в течение 10 секунд? Может быть любой вариант, например response_timeout и т. Д.

  • Версия : node_redis v.2.6.5 и Redis 3.0.7
  • Платформа : Node.js v5.5.0 на Ubuntu 14.04.4 LTS
  • Описание : ошибка retry_strategy остается невыявленной
pending-author-input

Самый полезный комментарий

Какие-нибудь Новости ? У меня точно такая же проблема.

Все 19 Комментарий

@pavelsc Я пытался воспроизвести это, но пока не смог.

Пожалуйста, попробуйте воспроизвести проблему без каких-либо сторонних модулей. В настоящее время вы, кажется, хотя бы используете q .

У меня такая же ошибка. Если я намеренно предоставляю клиенту Redis неверный URL-адрес, метод on.error не вызывается. Вот простой пример:

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

На данный момент я возвращаюсь к использованию connect_timeout, которое правильно выдает сообщение об ошибке после истечения времени ожидания подключения.

У меня та же проблема, использование настраиваемой retry_strategy с плохой конечной точкой приводит к ошибке «AbortError:»

Это зацепило меня и сегодня. Если коротко взглянуть на код, это может показаться преднамеренным поведением. https://github.com/NodeRedis/node_redis/blob/79558c524ff783000a6027fb159739770f98b10e/index.js#L405 явно заявляет, что если установлен retry_strategy , не выдавать ошибку, а вместо этого продолжает ее выдавать. Мне было бы любопытно услышать, почему это так, хотя, похоже, нет причины, по которой он не может испускать его вместо того, чтобы бросать с беглого взгляда. Есть ли причина, по которой это условие не может быть удалено, так что ошибка всегда будет выдаваться?

У меня тоже постоянно возникает эта проблема.

Я тоже не могу отловить ошибки при получении 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;
}

с участием:

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

Отлаживая приложение, я перехожу к проверке ENOTFOUND как указано выше в retry_strategy но она не вызывает обработчик событий ошибки.

У меня такая же проблема. Покопавшись в исходном коде, я обнаружил, что если мы изменим
эта строка (или включить режим отладки)
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L381 -L382

И вставьте сюда этот фрагмент кода (сразу добавьте ошибку в массив)
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L352 -L353

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

Он работает и выдает ошибку правильно.

Вложенный цикл в этой функции не выполняется, потому что command_queue пуста, а ошибка никогда не добавляется в массив и, следовательно, не генерируется. Если я правильно понимаю, это довольно старый фрагмент кода, поэтому нам нужен ввод от сопровождающих или от @BridgeAR

Я также видел, что при первом неудачном соединении генерируется событие «конец», которое может что-то означать (или нет), я взял Redis два дня назад, поэтому еще не уверен, как работают внутренние компоненты. Я постараюсь раскопаться и покопаться еще немного, когда / когда у меня будет время.

И буквально с этой проблемой, похоже, связана следующая проблема # 1198.

@ v1adko Сейчас я путешествую, но постараюсь взглянуть на нее сегодня или завтра (если только Рубен не опередит меня).

У меня намеренно неправильный URL-адрес redis для проверки сценариев ошибок, но я вижу, что моя retry_strategy не вызывается при попытке подключиться к redis. retry_strategy вызывается только при закрытии соединения.

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

`

Может ли кто-нибудь помочь мне решить эту проблему.

То же самое. Этот неприятный прием, кажется, создает ожидаемое поведение, но не уверен, имеет ли он какое-либо более широкое значение:

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

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

У меня сейчас те же проблемы. Использование retry_strategy, возврат ошибки, как указано в примере в Readme, но клиент не выдает ошибки. Исправления, предложенные @ v1adko, решают проблему хотя бы номинально.

Мне интересно, что это за обратная несовместимость, упомянутая здесь?
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L380

Как указывает @maael , поведение кажется преднамеренным, когда установлено retry_strategy. Ожидается ли такое поведение, но документация неверна? Должен ли я выдавать ошибки для клиента вручную, как это предлагает @ c24w ?

edit: Когда я копаюсь в пакете, я понимаю, что испускание вручную, вероятно, не путь вперед. Кажется, мне нужно понять упомянутые критические изменения.

Какие-нибудь Новости ? У меня точно такая же проблема.

Какие-нибудь Новости?

это неправильная идея:
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); }

Имея ту же проблему, retry_Strategy не запускает событие ошибки, еще не исправлено?

У кого-нибудь получилось?

Вместо этого мы переключили нашу реализацию на https://github.com/luin/ioredis , что принесло несколько улучшений (собственные Promises, lazyConnect (избегайте открытия соединения при создании экземпляра клиента Redis, помогло нам обрабатывать ошибки именно там, где нам было нужно)), и позволяет запускать следующий код:

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

Используя следующий 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;
};

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

Переменные env:

REDIS_URL=localhost:6379
REDIS_PASSWORD=mypasswordissostrong
Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

Mickael-van-der-Beek picture Mickael-van-der-Beek  ·  6Комментарии

id0Sch picture id0Sch  ·  4Комментарии

ghost picture ghost  ·  3Комментарии

abhaygarg picture abhaygarg  ·  5Комментарии

Stono picture Stono  ·  6Комментарии