【问题标题】:Redis (ioredis) - Unable to catch connection error in order to handle them gracefullyRedis (ioredis) - 无法捕捉连接错误以优雅地处理它们
【发布时间】:2020-04-10 12:03:24
【问题描述】:

我正在尝试优雅地处理 redis 错误,以便绕过错误并执行其他操作,而不是让我的应用崩溃。

但到目前为止,我不能只捕获 ioredis 抛出的异常,它绕过了我的 try/catch 并终止了当前进程。这种当前行为不允许我优雅地处理错误并从替代系统(而不是 redis)获取数据。

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
 *
 * @param url Url of the redis client, must contain the port number and be of the form "localhost:6379"
 * @param password Password of the redis client
 * @param 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.
 * @return {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);

    if (e.message === 'ERR invalid password') {
      logger.error(`Fatal error occurred "${e.message}". Stopping server.`);
      throw e; // Fatal error, don't attempt to fix
    }
  });

  return client;
};

我正在模拟一个错误的密码/url,以便查看 redis 在配置错误时的反应。我已将 lazyConnect 设置为 true 以处理调用者的错误。

但是,当我将 url 定义为 localhoste:6379 (而不是 localhost:6379 时,我收到以下错误:

server 2019-08-10T19:44:00.926Z [Redis client] error:  Error connecting to redis: "Error: getaddrinfo ENOTFOUND localhoste localhoste:6379"
(x 20)
server 2019-08-10T19:44:11.450Z [Read cache] error:  Reached the max retries per request limit (which is 20). Refer to "maxRetriesPerRequest" option for details.

这是我的代码:

  // Fetch a potential query result for the given query, if it exists in the cache already
  let cachedItem;

  try {
    cachedItem = await redisClient.get(queryString); // This emit an error on the redis client, because it fails to connect (that's intended, to test the behaviour)
  } catch (e) {
    logger.error(e); // It never goes there, as the error isn't "thrown", but rather "emitted" and handled by redis its own way
    epsagon.setError(e);
  }

  // If the query is cached, return the results from the cache
  if (cachedItem) {
    // return item
  } else {} // fetch from another endpoint (fallback backup)

我的理解是redis错误是通过client.emit('error', error)处理的,是异步的,被调用者不会抛出错误,也就是不允许调用者使用try/catch处理错误。

是否应该以非常特殊的方式处理 redis 错误?难道不能像我们通常处理大多数错误那样捕获它们吗?

此外,redis 在抛出致命异常(进程停止)之前似乎重试了 20 次连接(默认情况下)。但我想处理任何异常并以我自己的方式处理它。

我通过提供错误的连接数据测试了 redis 客户端的行为,这使得无法连接,因为该 url 上没有可用的 redis 实例,我的目标是最终捕获各种 redis 错误并优雅地处理它们。

【问题讨论】:

  • "不能只捕获异常来这样做,它总是崩溃。" - “崩溃”是什么意思?您在哪里看到记录的[Redis client] error 位?你读过Quick start吗?
  • 此日志来自 error 事件,它记录在我的 AWS CloudWatch 日志中。 “崩溃”是指当前进程在无法创建连接时从 redis 终止,我想捕获该错误,我目前无法控制它,它破坏了我正在尝试构建的工作流程,因为它绕过了我的 try/catch 尝试优雅地处理错误。
  • 您是说通过throw e; // Fatal error, don't attempt to fix 退出会“崩溃”吗?我对文档的阅读表明,连接错误,例如您正在使用的“无效密码”错误,首先报告为客户端对象上的 error 事件,并且只有在 maxRetriesPerRequest 尝试之后,挂起的命令才会“被因错误而刷新”。你在第一个错误时停止你的程序,所以你没有到达那一点。我认为如果您删除代码以在第一个错误时退出,您将在 // It never goes there, as the error isn't "thrown" 块中收到错误。
  • 谢谢@Nickolay,我在测试中迷路了,你帮我弄清楚了。确实,client:on:error 事件中不能有任何throw,我可能在测试时遇到了一些竞争条件,或者从那时起修复了一个错误,但现在它可以正常工作!
  • 酷!然后我将其发布为答案。

标签: javascript redis ioredis


【解决方案1】:

客户端 Redis 对象上的连接错误 are reported as an error event

根据"Auto-reconnect" section of the docs,ioredis 会在与 Redis 的连接丢失(或者可能是一开始就无法建立)时自动尝试重新连接。只有在 maxRetriesPerRequest 尝试之后,未决命令才会“因错误而被刷新”,即在此处到达 catch

  try {
    cachedItem = await redisClient.get(queryString); // This emit an error on the redis client, because it fails to connect (that's intended, to test the behaviour)
  } catch (e) {
    logger.error(e); // It never goes there, as the error isn't "thrown", but rather "emitted" and handled by redis its own way
    epsagon.setError(e);
  }

由于您在第一个错误时停止程序:

  client.on('error', function (e) {
    // ...
    if (e.message === 'ERR invalid password') {
      logger.error(`Fatal error occurred "${e.message}". Stopping server.`);
      throw e; // Fatal error, don't attempt to fix

...重试和随后的“错误刷新”永远没有机会运行。

忽略client.on('error'中的错误,你应该会得到await redisClient.get()返回的错误。

【讨论】:

  • 它对我不起作用。连接到集群时,抛出错误的唯一方法是在 clusterRetryStrategy 上返回“null”:clusterRetryStrategy: (times: number) => { return null;}。如果有人对集群有建议,请告诉我。谢谢。
【解决方案2】:

这是我的团队在 TypeScript 项目中使用 IORedis 所做的:

  let redis;
  const redisConfig: Redis.RedisOptions = {
    port: parseInt(process.env.REDIS_PORT, 10),
    host: process.env.REDIS_HOST,
    autoResubscribe: false,
    lazyConnect: true,
    maxRetriesPerRequest: 0, // <-- this seems to prevent retries and allow for try/catch
  };

  try {

    redis = new Redis(redisConfig);

    const infoString = await redis.info();
    console.log(infoString)

  } catch (err) {

    console.log(chalk.red('Redis Connection Failure '.padEnd(80, 'X')));
    console.log(err);
    console.log(chalk.red(' Redis Connection Failure'.padStart(80, 'X')));
    // do nothing

  } finally {
    await redis.disconnect();
  }

【讨论】:

  • 你把它放在一个函数里了吗?每次发出请求时你都连接redis吗?我仍然遇到未处理的错误
  • @AungMyintThein 将 enable_offline_queue 设置为 false 并阅读此内容和链接的问题 stackoverflow.com/questions/66893022/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-03-11
  • 2017-11-10
  • 2018-01-01
  • 1970-01-01
  • 2018-11-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多