最近用node+socket.io做手游服务端,在小范围的线上测试的时候发现有内存泄漏,一开始是以为是socket.io的问题
最后这几天通过heapdump分析,基本定位了是node-scheduled
这个库导致的(不知道是不是我使用问题)
heapdump分析
程序刚启动打一个snapshot,运行3个小时左右再打一个snapshot,对比这个2个snapshot发现有大量的closure(新增100W+),都是node-scheduled这个库产生的
node-scheduled主要用在游戏内道具的倒计时,一局游戏3分钟时长,6个玩家为一个房间,一个房间内有20个左右的道具 游戏一开始每个道具我都用node-scheduled启动一个task来通知客户端这个道具在什么时候出现 当玩家在游戏过程中吃掉一个道具后,客户端告诉我,我就重新给这个道具一个task用来倒计时多长时间后这个道具恢复(一个道具被吃掉后一般10多秒就恢复了)
每个房间有一个object对象room,我将这个房间内的所有道具的task都绑定在这个room对象上,当这局游戏结束的时候,我首先把房间内所有的道具task都cancel掉,然后再销毁这个room对象 但不知道为什么通过snapshot查看到还是有100W+的新增
服务器用的是双核4G,这个服务器上只允许了这个一个node进程,在运行4小时左右loop delay已经达到20ms+了,当前进程CPU快超过80%了,运行时间越长loop delay能达到上百,进程CPU超过100%
1.上述定时器我改用settimeout和setinterval会不会好一些? 2.对于只跑一个node进程来说 买多核有用吗?
node:8.4,node-scheduled:1.2.4
node-schedule 的场景和你这个发道具的的确不太合适,虽然我赶脚是使用姿势问题 其实我赶脚settimeout都不需要,你说到的这个场景,领取道具和得知下一个的冷却都是主动出发的,除了第一次以外都可以在领道具的时候返回 另外你这个架构我感觉可能会有一些小问题哇,比如单核,比如使用内存存储各种道具。。 有需要的话可以加个qq沟通一下
@aojiaotage 非常感谢,加你了
看起来还是像使用姿势不对, 不过每一个道具都绑这么复杂的东西没必要,用个集合维护下道具,处理下吃道具的event感觉就完事了啊.
为啥不在数据库或缓存里记验证的时间戳,用客户端来算倒计时,服务端只在使用的时候做验证.
@178220709 @baka397 实现上却是有点问题,我是接盘侠。听了各位的意见,正在修改中。。。
(nodejs)游戏服务器能不用定时器尽量不用定时器,能用时间戳尽量用时间戳
@nobody 嗯
可以看看这个时间轮库
这两天看到的文章,就实现了一个库出来
https://github.com/axetroy/wheel-timer.js
好处就是,用一个定时器,不断的tick(假设一秒tick一次)。
然后这个定时器转一周之后,在tick另一个定时器。
例如:
- 秒定时器
- 分定时器
- 时定时器
每秒转动一下秒定时器,秒定时器转动一周(60),则转动一下分定时器…一次类推。
每个定时器,每次转动,就代表有数据要过期…
可以在回调里面监听哪些数据过期,然后对数据进行处理…
@devuserxky sss
@axetroy 谢谢
举个例子,道具冷却时间。这个道具可以无限使用,冷却时间为60秒.
const HashWheelTimer = require('wheel-timer');
// 定义秒定时器
class SecondTimer extends HashWheelTimer {
constructor() {
super(60);
}
tick() {
const beforeRound = this.round;
super.tick();
const afterRound = this.round;
if (afterRound > beforeRound) {
// 如果秒针走了一圈,则让分针进入到下一刻度
minute.tick();
}
}
}
// 定义分定时器
class MinuteTimer extends HashWheelTimer {
constructor() {
super(60);
}
}
const second = new SecondTimer();
const minute = new MinuteTimer();
const item = {name: "道具名"}
// 这里调用使用物品
function userItem(){
// 判断物品是否在冷却
if(minute.map.has(item)){
// 如果是在冷却,说明不能使用物品
return
}
// 在这里使用物品
// 比如增加xxx属性之类
minute.add(item) // 把物品调用放在分表里面,等待冷却
}
minute.on('tick', (data) => {
// 分针被推动了,说明前一分钟的数据(道具)已过期
// 这些数据已经从时间转盘中删除,然后调用这个回调函数
});
setInterval(() => {
second.tick(); // 每间隔1秒,推动一下秒针
}, 1000);
@imhered 恭喜楼主有进展了,你可以看看我的这个文,一种因闭包引发的内存泄露 希望对你有帮助
是是是
查了一下 node-schedule 底层还是 settimeout实现的哦·
@ipengyo 嗯。 下午4点做了一次修改,bug应该修了。 跑到现在目前还没出现问题
@imhered 老哥求指教 具体是哪里出了毛病 让我也学习一下, 我最近也准备使用nodejs做游戏服务端~~
@ipengyo 问题很简单,每次执行一个task后cancel掉就行了,我创建了好多只执行一次的task我以为执行完了就不用管了。看了源码,就算是执行一次的task也必须cancel,不然这个task一直会存在于cache中
不过建议能不用它的还是不用,感觉我目前的需求用这个有点浪费,我是接手别人的项目才写成这样的
@nobody 时间戳是定时器的完美取代品!
@baka397老哥, 时间戳怎么代替node-scheduled 能稍微多说一点吗
@axetroy 我在自己项目中也造了个tick ,原因是很多地方用到setTimeout ,想找个地方统一管理一下,结果写成:
co(function *(resume){
while(0 == 0){
yiled setTimeout(resume,1000);
// check the job list
}
})()
这样的形式,后来想想有点傻逼,setTimeout 实质也是个大的loop ,而且还是C++的,感觉有点多此一举
学习一下