背景:刚开工第一天,正值正月初七,公司的客服相应出现socket链接断掉被自动退出登录的问题,刚开始还以为是网络问题,后来开始大面积出现,于是开始了找bug之旅。
刚开工,没发布过代码,而且正好第二天就出现了,于是就开始调研出问题的客服,因为没发布过代码,开始调查发生问题的人,他们的chrome版本都是88,看了下chrome更新日志,正好发布过一个版本,查看它的更新日志 发现有一段更新
怀疑对象来了,简单的说就是为了节省cpu和省电,chrome会对于处在非当前tab的定时器采用每分钟才跑一次的处理,先验证这个是否是真的。
开启一个定时器,每秒跑一次,开启多个浏览器tab,让开启定时器的tab不在当前页面,等待一段时间,得到结果如下
居然有这么大的间隔误差,而且跟说好的每一分钟跑一次也不一致啊,chrome在演我?也可能是我挂机的时间不够长
原因找到了之后,再仔细排查出现的问题,可以将问题分成两个:
- 定时器惰性执行导致心跳包发送不及时
- 对于在线时长计算的定时器出现计算误差
定时器惰性执行导致心跳包发送不及时
因为chrome过长时间没有发送心跳包,所以导致服务器认为客户端链接已经无效,直接进行断开处理,对于问题1,目前想到有几种解决的方案
- 监听visibilitychange事件,切换tab的时候,会有一个hidden的属性,可以在hidden的时候,给服务端发送一个通知,告知其这是暂时离开,可以暂时不用关闭连接
- 心跳包由后端发送,前端接收到这个事件给予反馈证明连接还需要保持,这样就没有定时器的问题了
- 利用webworker处理,待会儿详细说说这个做法
- 后端加大心跳包间隔时间
- chrome://flags/#intensive-wake-up-throttling,将其disabled掉
个人认为第二种处理方式是最妥当的,因为chrome限制的是定时器的执行,但服务器不会有这种骚操作,但由于服务端改动起来不太好,所以又想到用webworker去处理了。
WebWorker是HTML5新出的一种方案,其用途是帮助js解决耗时任务卡住js线程的问题。印象中看过一篇文章是说webworker是运行在后台的,所以猜测定时器可能不会受影响。后来直接开启验证,贴上worker代码
// worker.js
let preTime = Date.now();
let timer = setInterval(() => {
let now = Date.now();
const timeDiff = Math.floor((now - preTime) / 1000);
preTime = now;
self.postMessage({
type: 'heart-beat'
});
}, 10 * 1000);
self.addEventListener('message', () => {
const { type } = e.data || {};
// 主线程通知关闭当前线程
if (type === 'interval-thread-close') {
clearInterval(timer);
self.close();
}
});
// main.js
const worker = new Worker('worker.js');
worker.onmessage = (e) => {
if (e.data.type === 'soft-phone-heart-beat') {
// console.log('发送心跳包');
this.doSend('heartBeat');
}
};
经验证,确实不会受影响,这也太强了。
webworker还有几个点需要注意
- worker文件跟页面必须得同域
- 不能操作dom
- 与主线程不在同一个上下文
问题1解决。。。
-------------------------- 分割线 -------------------------
对于在线时长计算的定时器出现计算误差
这个问题网上也有解决方案,就是记录住上一次进入定时器的时间戳,当下一次进来的时候,用当前时间戳减去上一次时间戳,得到时间差,这个就是时间间隔了,代码如下:
let preTime = Date.now();
const timer = setInterval(() => {
const now = Date.now();
// 算出时间差
const timeDiff = Math.floor((now - preTime) / 1000);
// 由于定时器不一定是1000ms一次,有时候992,996都有,所以这里的数有可能小于1
let count = timeDiff < 1 ? 1 : timeDiff;
preTime = now;
}, 1000);
问题2也解决了。。。
但还有些谜团没解开
- 相同浏览器版本的人,相同操作系统的人,为什么部分人会,部分人不会(猜测是chrome的灰度机制,之前就被第三方cookie问题坑过)
- 降级到87版本,然后再升回去,为什么之前出问题的人突然又正常了
- 说好的1分钟运行一次定时器,为什么我的mac间隔时间不定的,有8s,10s,15s
- 按照chrome://flags/#intensive-wake-up-throttling enabled,发现定时器还是依然准确计时
chrome真的是谜。。。