最近遇到了一次服务端定时器性能瓶颈的问题,把解决过程在此记录一下,作为备忘。
首先是前一段线上项目连续导人,平均在线千人左右,随之服务器慢接口(>100ms)越来越多,一天能达到五位数。观察内存占用情况,不高,没有频繁old gc的情况。观察cpu占用率,达70%-90%。这个数据是比较高的,比一般同等人数在线的游戏高出不少。这说明出现这种情况不太正常,并不是单纯在线高需要分服务器的问题,可能代码中某部分存在性能瓶颈。
顺着这个思路,在慢接口中的关键节点加日志调试,发现运行时间长的是一段添加新定时器的代码。这段代码是按时间顺序,将定时器添加到保存列表的一个合适位置,执行过程中会加全局锁。怀疑是定时器数量太多时,这段代码执行慢。用Jstack查看线程状态,也侧面印证了这一点,多个线程都在等待这段代码使用的全局锁。仔细研读这段代码,发现它使用LinkedList
来保存所有定时器,所以插入和移除的时间复杂度都是O(N)。考虑改成PriorityQueue
,这样插入和删除都降为O(logN)。
新代码上线后,慢接口大幅度减少,cpu占用也降到20%左右。说明优化起到明显效果。
总结几点:
- Jstack真的很有用。除了可以看死锁,还可以看线程运行状态、锁竞争情况,多次Jstack下都在Runnable状态的代码往往是执行较耗时的。
- 做好日志监控。慢接口数量、平均响应时间、平均接口数据包大小、内存占用、cpu占用,这些一有异常,能够及时被通知到。主动接受监控报警,而不是被动接受玩家反馈。
- 一定要做好压测。为今后一段时间设立目标,按预估负载,尽量模拟线上真实情况。对于怀疑可能产生瓶颈的代码重点压测。做到早发现,早解决。