本周主要工作是解决dbd的多线程bug,可是失败了。
为了重现这个多线程的bug,准备模拟多用户登录的情景,当多个用户需要保存数据的时候,就会跑到多线程序列化数据的部分,应该可以重现这个bug。于是,从周一到周三,花了三天时间,用python+gevent写了一个模拟客户端,实现了客户端连接服务器和登录的流程。其实这个测试工具,在上一个项目里已经写了一点了,基本数据类型的解包比如整型,字符串解包已经有了。但是,我翻看代码才发现,当时没有写打包协议的实现……写的过程比较流畅,用了mixin的方式将rpc协议独立出来一个RpcMixin类,这个类最后会被SimulateUser类继承,而SimualteUser则负责提供连接好的socket,以及相应的greenlet协程、登录用户名。唯一比较严重的bug,是启动时并发多个greenlet协程的时候,用了range而非xrange,又手残多填了几个0,结果CPU和内存都耗在了产生range(1000000000)这个巨大无比的整数列表上了。
解决完以上的问题,周四到周六就开始正式的压测过程。压测出来的结果比较诡异,主要情况是多玩家登录后,再断开连接,而gamed(游戏逻辑进程)和dbd(游戏数据储存进程)依然保持着高水位内存。有一次还显示2000多个在线玩家,但模拟客户端已经全部断开连接了。这个有可能是进程最大fd连接数引起的,错误处理没做好,fd最大值设成200000后就没重现过这个问题了。
维持高水位内存,目前猜测是两种可能。一是内部实现了内存池之类的设施,一经申请后再不释放,因为上到高水位后,断掉模拟客户端,再重新连接进行压测,内存也不再增长,只保留在高水位。二是有内存泄露,因为高水位后断开模拟客户端,过一晚上之后内存还有轻微增长。翻看了一下引擎的文档,里面有一点前辈留下的关于内存泄露排查的记录。里面提到的主要是脚本层的内存泄露排查,提供了一些simul_efun去dump出所有内存里的脚本object,各个object的引用数和大小。排查了一遍object,只有一屏幕不到的类型,很快确定没有问题。单纯客户端连接登录,不做任何操作,脚本层对象的大小才600+MB,这时候已经差不多11000的模拟用户登录了。然后再排查了一下基本类型,主要是字符串,mapping,array这三种。数据也比较正常,无法拼凑出gamed 1G多的内存占用。
EDIT:gamed引擎里的确有pool的概念,一经申请再不释放。在玩家管理里,用到了user_stack,断线释放的玩家结构数据块都会放在这里。