优化C#服务器始末
0.背景
接到任务需要帮助其他项目组解决游戏服务器卡顿问题.游戏服务器使用了与客户端相同的语言--C#编写.
1.工具
1.1 C#的代码热点分析工具
服务器卡顿造成游戏卡顿,大致分为两种情况:
- 某些代码存在计算热点,占用了较多的CPU计算资源.
- 某些代码存在IO热点,造成了程序阻塞.
这两种情况都需要工具对程序的执行热点进行分析,考察了dotTrace,ANTS Performance Profiler,EQATEC Profiler,······【https://blog.csdn.net/weixin_30539835/article/details/95806855】等工具,最终选用了dotTrace.
dotTrace的使用方法见文章 使用Dottrace分析C#程序性能瓶颈
1.2 压测工具
单有代码热点分析工具还不够,还需要有机器人模拟玩家对服务器进行压力测试.这样才能触发服务器对应的逻辑.结合代码热点分析工具能够定位热点函数.然后进行优化.
机器人文档见 一种游戏机器人实现
2. 分析过程
一开始得到的信息是怀疑玩家的登录操作和做新手任务的过程中某些地方会造成服务器卡顿.于是使用机器人和行为树完成了登录逻辑和完成新手任务功能.并对这个单进程服务器压了600人进行登录以及跑新手任务.在解决了几个热点问题后,线上卡顿问题仍然存在.
得到更详细的线上信息是每一个半小时都会卡个几秒钟,并且服务器的40多G内存会大部分被占用.于是转变思路,开始分析服务器的内存增长情况. JetBrains 中也有内存分析工具 dotMemory,[使用方法见文档: 使用dotMemory分析C#程序性能瓶颈].
使用dotMemory以及机器人服务器程序进行压测,发现服务器程序中每个新建的连接都会new一个3MB大小的内存块用作发送缓冲区,还new一个3KB大小的内存块作为接收缓冲区。断开连接后这两块内存交给GC来回收.(GC这种操作在C/C++中没有,因此一开始没有想到还有这种操作).
至此,在第一节所述的"服务器卡顿造成游戏卡顿"的情况应该加上第三种了:GC回收大量内存时会造成卡顿.
解决方法是写了一个内存池来复用缓冲区.经过dotMemory测试,新增内存中已经没有对应的大块内存增长了.这个解决方法经过内网测试后应用到线上后,内存总体平稳了,不再会把服务器的内存占用完了.但是从细节处看,内存仍在增长,然后被系统自动GC,从而达到的总体的"平稳"。这说明代码中仍有大量new出来的内存时托管给GC来回收的.
通过dotMemory提供的线索以及分析对应代码,与客户端建立的连接接收处的代码为了"拼接"接收到的协议包数据,每次都会new出来内存用于"拼接",并且在解包处每解一个包都会再new一个内存块传出剩余的数据(令人发指).
解决方法是使用环形缓冲区来缓冲接收到的数据.
3. 总结
优化五步走:
- 选准分析CPU热点或者内存增长点的工具
- 使用机器人触发对应的逻辑.
- 分析CPU热点或者内存增长点.
- 提出解决方案.
- 重复1、2、3步骤检测解决方案是否达到预期效果.
优化之路漫漫其修远兮