ioredis icon indicating copy to clipboard operation
ioredis copied to clipboard

一个关于 redis 长链接的问题

Open ZhangDianPeng opened this issue 3 years ago • 3 comments

我们在使用 ioredis 过程中遇到一个问题,连接的 redis 服务是通过 nginx 四层负载做的主从切换的代理,如果 redis1 可以连通就直接连接 redis1,否则切换到 redis2。但是 node 客户端永远都去连接 nginx:6379。

现在遇到一个问题,就是当 redis1 挂掉以后(比如直接电源关机),这个时候 node 客户端这边大约要等10 分钟左右才能成功连接成功,中间一直 hold 没反应。这个我们测试了下和客户端本身的 tcp.keepalive 配置有关:

net.ipv4.tcp_keepalive_time=7200 net.ipv4.tcp_keepalive_intvl=75 net.ipv4.tcp_keepalive_probes=9 感觉像是 75 * 9 的时间,确实把系统这个参数改小可以短时间内完成切换。

我们全局 redis 只用了一个长链接,也没有什么连接池之类的,不知道大神之前遇到过类似问题吗?java 相关的服务使用连接池好像没这个问题,我们 redis 配置如下:

redisClient = new Redis(tempRedisParams.port, tempRedisParams.host, {
            password: tempRedisParams.pass,
            connectTimeout: tempRedisParams.connect_timeout,
            enableReadyCheck: false,
            maxRetriesPerRequest: 20,
            keepAlive: 3 * 1000,
            retryStrategy: function (times) {
                console.log('redis.retryTimes:', times, 'clientIndex:', clientIndex, sign);
                let delay = Math.min(times * 100, 5000);
                return delay;
            }
        });

想问下有没有遇到过类似的情况?

ZhangDianPeng avatar Jun 29 '22 03:06 ZhangDianPeng

我想类似的 issue 是这个:https://github.com/luin/ioredis/issues/275 。是你描述的问题吗?

luin avatar Jun 29 '22 04:06 luin

我看了下,和 https://github.com/luin/ioredis/issues/139 这个 issue 比较像,看起来是 node 本身不支持 tcp_keepalive_intvl 和 tcp_keepalive_probes 两个参数的配置,所以 ioredis 也没办法支持。因此连接断开基本需要消耗 75* 9 = 11 min。

看来只能通过在客户端侧添加心跳主动去断开连接来实现了,晚点试下看看,多谢大神。

ZhangDianPeng avatar Jun 29 '22 05:06 ZhangDianPeng

我看了下,和 #139 这个 issue 比较像,看起来是 node 本身不支持 tcp_keepalive_intvl 和 tcp_keepalive_probes 两个参数的配置,所以 ioredis 也没办法支持。因此连接断开基本需要消耗 75* 9 = 11 min。

看来只能通过在客户端侧添加心跳主动去断开连接来实现了,晚点试下看看,多谢大神。

这里没发现更好的方式解决这个问题,下面是我解决这个问题的代码

  const delay = (t, v) => new Promise((resolve, reject) => {
    setTimeout(reject.bind(null, v), t);
  });
  const heartBeatFunc = async () => {
    let serverStatus;
    try {
      serverStatus = await Promise.race([
        client.ping('isAlive'),
        delay(5000, 'Ping command timeout'),
      ]);
    } catch (error) {
      app.coreLogger.error('[egg-data:redis] ping 报错', error);
    }
    app.coreLogger.info(`server status: ${serverStatus}`);
    if (!['isAlive', 'pong', 'PONG'].includes(serverStatus)) {
      app.coreLogger.info(`ping status: ${serverStatus} connection disconnect`);
      await client.disconnect();
      await client.connect();
    }
  };

  if (app.config.eggData.heartbeat.enable) {
    let beat = app.config.eggData.heartbeat.interval;
    beat = beat > 0 ? beat : 30;
    schedule.scheduleJob(`*/${beat} * * * * *`, () => {
      heartBeatFunc();
    });
  }

FantasyNeurotic avatar Dec 19 '23 02:12 FantasyNeurotic