贴上原文地址:http://blog.csdn.net/gz80/article/details/7408658
游戏上线三个多月,途中经历了宕机、卡死、回档、断线、间歇性抽风、断续登陆失败等无数劫难,在团队内各位专业精英的努力,现在终于到达了一个较稳定的阶段。
现总结一下服务器压力的原因和解决方案。
对于游戏服务器来说,压力无非来源于三座大山:数据库、网络以及系统资源(包括CPU、内存、硬盘、网卡等)
(1)众里寻他千百度,蓦然回首,那人却在灯火阑珊处——海量数据的化整为零处理。
在开发阶段,我们只是面对自己创建的约几千条测试数据进行处理。然而一旦上线,面对海量玩家的涌入,如果数据效率没注意好的话,很容易就卡壳。
记得我们封测后第一次宕机,原本以为开了进程监控能马上自动重启,结果等了半天依然没反应。中断进去看了下线程状态与函数堆栈,发现原来卡在账号的预加载中。
再检查一下账号预加载的sql语句,竟然有n个表联合查询,又GROUP BY,几个LEFT JOIN,还有ORDER BY排序。我们COUNT了一下数据库中的玩家记录,原来已经几十万条了,怪不得Mysql顶不住压力怠工了。
在我们改进了sql语句后,将一句复杂查询分解为多句简单查询,大大提高了加载速度。穆帅在国米时说过,他宁愿要五场1比0,也比一场5比0好。把一句复杂的语句化整为零,拆分成简单语句,能使程序效率有质的飞跃。
在项目上线后,我们曾调查过每个sql语句执行时间的记录,结果发现其中的账号成就信息加载非常耗时。经过检查成就数据表后发现竟然没有加索引。加上索引后,其加载速度就有了突飞猛进。
MySql使用的索引是B-树,根据B-数的数据结构可以得知它是一种平衡树。
假设记录数为N,则它的搜索时间复杂度为O(logN)。而对于无索引的数据进行顺序搜索,则时间复杂度是N/2,即O(N)。根据logN/N的函数曲线,以及lim N→+∞ [O(logN)/O(N)] = 0的极限可以看出,当数据量(N)小的时候差别不明显,但对于海量数据的话,差别就相当大了。
而且B-树是一个顺序树,也就是在插入时已经排好序了,因此对索引列进行ORDER BY排序的操作耗时几乎是可以忽略的。当然索引也有一定的缺点,就是对于插入和删除操作,它需要锁定并重建索引表。这会耗费数据库操作时间,所以对于插入频繁的log类型表进行索引反而会适得其反。
具体mysql中B-树的数据结构请参考《MySQL索引背后的数据结构及算法原理》
http://tech.it168.com/a2011/0711/1216/000001216087.shtml
面对开服后日积月累的玩家与日志数据,无论再怎么优化对性能的影响已经无能为力了,这时就需要对数据库进行瘦身整理。
整理的方法一般有两种:
一是简单粗暴的删档。对某些条件,如超过x天没登陆的账号进行删除,其相应的其余记录也连带删除。
二是数据库分级,就像像银行系统那样,建立两个数据库:活跃库与休眠库。定时把不活跃账号搬移一个休眠库中。当需要查询用户信息时,先从活跃库查询,查询不到再去休眠库查询。如果从休眠库中查到,就将这咸鱼翻生的用户记录搬回活跃库。
(4)无可奈何花落去,似曾相识燕归来——利用缓存应对玩家刷新
把数据记在内存中肯定比记在硬盘中高效。玩家下线后并不急着析构玩家对象,而是放在缓存内,因为我们无法得知他是刷新页面还是真正下线,这样可以缩短他再次登录的加载耗时。
同时还要根据系统内存使用状况进行整理,优先释放那些最近没被命中的对象,保留最近命中的对象,也就是缓存调度LRU算法的实现。
根据经验特别是在系统有问题或网络卡时,玩家最喜欢频繁刷新页面,因此缓存机制在这种情况下能大大减轻数据库的读取压力。