一个关于 redis 长链接的问题
我们在使用 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;
}
});
想问下有没有遇到过类似的情况?
我想类似的 issue 是这个:https://github.com/luin/ioredis/issues/275 。是你描述的问题吗?
我看了下,和 https://github.com/luin/ioredis/issues/139 这个 issue 比较像,看起来是 node 本身不支持 tcp_keepalive_intvl 和 tcp_keepalive_probes 两个参数的配置,所以 ioredis 也没办法支持。因此连接断开基本需要消耗 75* 9 = 11 min。
看来只能通过在客户端侧添加心跳主动去断开连接来实现了,晚点试下看看,多谢大神。
我看了下,和 #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();
});
}