因为项目上用到WebSocket,并且后端有使用nginx转发,所以在前端用setTimeout做了心跳维持的递归间隔循环,防止nginx超时切断连接。
实际使用时,nginx超时时间设置的60s, 前端循环心跳是50s间隔,但是发现时不时就会因为nginx超时断开。
问题排查
该问题会出现在谷歌内核的浏览器中。
监控前端的WebSocket消息发送记录,发现在切换标签后,心跳间隔会变成51s,过了一会之后,间隔会进一步延长至60s。
因为消息发送需要时间,所以在切换标签之后,会在1~2轮心跳之后,nginx超时切断WebSocket连接。
问题测试
查找了网上的资料之后,初步推算问题应该是谷歌浏览器内核的优化导致的问题。
但是查到的资料都是以下说法:切换标签之后会进入非激活状态,setTimeout的最短间隔时间会变成1秒。
网上说法与我遇到的情况不同,于是自己做了下测试。
测试代码:
function sleep(millisecond) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, millisecond);
});
}
const logTime = async (sleepTime) => {
const time = new Date().toTimeString().split(' ')[0];
console.log('time:', time);
await sleep(sleepTime * 1000);
await logTime(sleepTime);
};
logTime(60);
测试结果(参数 5s):
time: 11:59:25
time: 11:59:30
// 切换标签
time: 11:59:36
time: 11:59:42
time: 11:59:48
// 回到页面
time: 11:59:57
// 最小化
time: 12:00:57
// 回到页面后立即执行了一次
time: 12:01:30
time: 12:01:35
测试结果(参数 60s):
time: 10:10:26
time: 10:11:26
// 浏览器切换标签
time: 10:12:27
time: 10:13:28
time: 10:14:29
// 浏览器最小化
time: 10:15:30
time: 10:16:31
time: 10:17:32
time: 10:18:33
测试结果(参数 70s):
time: 11:14:29
time: 11:15:39
time: 11:16:49
// 切换标签
time: 11:18:00
time: 11:19:11
time: 11:20:22
time: 11:21:33
// 最小化
time: 11:22:57
time: 11:24:57
time: 11:26:57
问题总结
由于谷歌浏览器内核优化,setTimeout会根据页面的状态,对延迟时间进行调整。
大致的延迟规则如下:
- 非当前标签最小化或休眠:等待时间向上取整分钟,如果设置时间就是整分钟的,+1s
- 1s ~ 59s --> 60s
- 60s --> 61s
- 61s ~ 119s -> 120s
- 切换标签:延迟时间 +1s
- 回到页面时会根据计时时间判断执行,具体逻辑不清楚