思考游戏服务器的tick机制

游戏服务器程序内部都会统一设计所谓的tick机制,这个机制一般来说有两个用途,一是许多业务模块都会有定时处理的需求,如:技能Buff计时、时间道具检查、定时给玩家收益(打坐定时回血和蓝,跳舞定时回经验等)等等;二是游戏服务器内部会有一些日常主动驱动的事件,如:怪物移动、玩家移动等,这些需求都需要用到这个tick处理,夸张一点说,游戏服务器业务逻辑的处理绝大部分都会有涉及到tick处理的需求。

那这个相当重要的tick机制是如何实现的呢?目前我们采用的是在主程序大循环中,每一次循环取一次当前系统时间并保留上次进入tick处理时的时间,然后拿这两个时间相比较,若大于tick设定的间隔时间(如:200ms),则进入tick函数处理。这个tick函数中,注册了系统所有业务模块需要定时处理的接口。于是,在大循环中每一次tick调用,就会将系统所有定时处理的接口都处理一遍。于是,问题便也随之而来了… …

显而易见的问题是,所有的定时处理都放在一起调用,这会造成CPU的峰值瞬间拉高,并且也会造成server消息处理的能力下降(因为tick处理的这段时间,本质上是可以作为server对消息的延时处理时间),对于这个问题,我们也有显而易见的处理方法,即将各个业务模块的定时时间进行分类,大致可以分为200ms、500ms、1s、3s、20s、30s、60s等适合各业务需求的定时间隔,这样每一次tick调用时,就不会同时有许多模块一齐处理了,这可以在一定程度上平滑CPU的处理曲线。但也无法避免会在某个时间点,同时有许多(甚至所有)业务模块定时处理的情况,所以,我在想,是否还有其它更好的处理办法呢?

首先想到的是,是将各个业务模块的定时间隔进行更精确和合理的分类,比较理想的情况是:使各个定时间隔互不包含,即各个间隔之间不要存在整除的关系,如:300ms、500ms、1100ms等,这个可以做一个测试,看一下这样修改后,CPU的处理是否更平滑一些。

由于在linux下中,系统定时器是由SIGALRM信号来实现的,而一般游戏server为了稳定等原因,都屏蔽了linux的许多信号操作,所以,一般就采用自定义的定时器来实现tick机制了,只是我们的这种机制是否还有更好的实现方式呢?这的确是一个值得思考的问题,毕竟,tick处理在游戏server中占了相当大的比重。

当然,只有让每一个业务模块,每一次tick处理的时间尽可能缩小到最短,那么,tick处理对于CPU的消耗就会减轻到最小了!