Node-redis: 从 retry_strategy 返回的错误仍未捕获

创建于 2017-02-27  ·  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 跟踪脚本:Redis 客户端:连接
21:00:14.695Z 跟踪脚本:Redis 客户端:准备就绪
21:10:23.837Z 跟踪脚本:Redis 客户端:结束
重试策略检查
{ 尝试:1,
错误:{ [错误:Redis 连接到 redis.callision。 信息:6379失败 - 读取 ECONNRESET] 代码: 'ECONNRESET', errno: 'ECONNRESET', syscall: 'read' },
total_retry_time: 0,
连接时间:1 }

/node_modules/q/q.js:155
扔e;
^
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)
在套接字。(/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
  • 平台:Ubuntu 14.04.4 LTS 上的 Node.js v5.5.0
  • 描述: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 的输入

我还看到,在第一次失败的连接上会发出 'end' 事件,这可能意味着什么(或不是),我两天前拿起了 Redis,所以不确定内部是如何工作的。 如果/当我有时间时,我会尝试分叉和挖掘更多。

从字面上看,下一个问题似乎与这个问题有关 #1198

@v1adko我目前正在旅行,但我会试着在今天晚些时候或明天看看它(除非鲁本打败了我)。

我故意将我的 redis url 错误地测试错误场景,但是我看到在尝试连接到 redis 时没有调用我的 retry_strategy。 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;

}

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

`

有人可以帮我解决这个问题。

同样在这里。 这个讨厌的黑客似乎创造了预期的行为,但不确定它是否有更广泛的影响:

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

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

我目前遇到了同样的问题。 使用 retry_strategy,返回自述文件中示例所示的错误,但客户端没有发出任何错误。 @v1adko提出的修复至少从表面上解决了这个问题。

我想知道这里提到的向后不兼容是什么?
https://github.com/NodeRedis/node_redis/blob/009479537eb920d2c34045026a55d31febd1edd7/index.js#L380

正如@maael所指出的,该行为似乎是在设置 retry_strategy 时有意为之。 那么行为是否符合预期,但文档不正确? 我应该按照@c24w 的建议手动为客户端发出错误吗?

编辑:当我深入研究包时,我意识到手动发射可能不是前进的方向。 似乎我需要了解提到的重大变化。

任何新闻 ? 我也有同样的问题。

任何新闻?

是一个错误的想法:
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();
    });
  });
});

环境变量:

REDIS_URL=localhost:6379
REDIS_PASSWORD=mypasswordissostrong
此页面是否有帮助?
0 / 5 - 0 等级

相关问题

id0Sch picture id0Sch  ·  4评论

dotSlashLu picture dotSlashLu  ·  5评论

juriansluiman picture juriansluiman  ·  3评论

Mickael-van-der-Beek picture Mickael-van-der-Beek  ·  6评论

Atala picture Atala  ·  3评论