|
在历史工程上修补是件麻烦的事情。
前两天说起梦幻西游服务器的优化。这几天我到广州住下来,打算专门花一周时间搞定这件事。由于以前都是网上聊天,只有坐到一起才能真正理解问题。
目前,梦幻西游,只使用单台机器,最高配置 8 个 CPU ,配置 8G 内存。就算最热闹的服务器,也用不完这些资源(大约只用满了 3 个 CPU ,一半的内存)。核心程序差不多就是 10 年前写的,从大话西游延续至今。这两年一直在享受免费的午餐,随着硬件配置提升,现在单台服务器同时在线容量达到一万两千人。观察服务器回应速度的图表可以发现,目前的问题在于,定期会出现反应迟钝的现象。周期性的,服务器回应时间会超过 1000ms 。查得原因在于那个时候,磁盘 IO 非常拥塞。有定期保存玩家数据的服务对 IO 的占用,以及 SA 做的定期备份数据的脚本占用了大量的 IO 时间。最终造成了机器负荷过重。
IO 负荷过重最终怎样影响到游戏服务的性能,这个暂时不过于深入探讨。我这两天主要是分析以有的系统结构,并想一下改进方案。
其实老的系统并不复杂,代码量也相当之小。相关的服务代码仅仅数千行干净的 C 代码而已。一直没有人动它,因为事关重大,牵扯着数百万用户的数据,以及记费流程。无论设计是好是坏,实现的性能有无问题,都让位于稳定。“历史原因”造成的种种,也只能在闲聊时抱怨一句,如果重新设计,肯定不会这样写了。近两年,我越发的对重构这件事情显的兴趣漠然,为何不这样做,为何不那样? 更多的时候都只是程序员们饭局上的聊资。每个系统一旦编写完成,就充满了种种的遗憾。如果它能用,最大的可能就是它就将一直用下去。一切的新想法,留给下一次吧。
对于已经稳定运行了很多年的陈旧的系统,找到好的方法去改造的意义不大。最重要的是,如何对已有系统影响最小的增加一些东西,提高性能。模块间清晰的划分显得相当重要。服务的独立性也是必要的。现在运行的数据服务和记费以及用户鉴权服务居然放在一个服务程序中恐怕是一个大失误。它使得我们把数据读写剥离出来非常困难。
数据服务采用的是一个 C/S 结构。但没有使用数据库,而是直接使用的本地文件系统。整个设计算是良好,但数据服务本身的机制却很糟糕。C 和 S 之间采用共享内存交换数据,这是为了提高 IPC 性能。C 只有一个,就是游戏主进程,而 S 可以有多个。可以并发的提供服务。多个 S 和 C 之间用管道传输命令,用共享内存交换数据。本意是好的,但协议设计是有问题的。因为 C 直接操控数据区,而有唯一性,结果设计时,把数据区的区块管理放在了 C 上,而不是由 S 提供。
举例来说,如果游戏进程(C) 需要加载一个用户的数据,它自己先寻找数据区中的空位,然后通知 S 把这个用户的数据加载到它指定的数据位置。数据区的清理工作同样是由 C 这边做的。这使得 S 不能直接在数据区上做 Cache ,如果需要 Cache 暂时不用的数据(比如一个玩家离线)就得由 C 自己来做。或者额外的再做一个 Cache 服务(这需要多出一倍的内存,以及内存复制的操作)当初这么实现恐怕是考虑到有多个 S 同时为一个 C 服务的需求,但我只能认为是设计欠佳。
结果就是,整个数据服务,无论是读还是写,都是无 Cache 的。Cache 仅仅依赖 OS 来做。对于当初低一个数量级的时候,这没有问题。但在线人数从千级达到万级后,问题就显露出来了。毕竟你为最终需求最更多的定制,越能充分发挥硬件的性能。
下面记录一下我已经实现好的内存 Key/Value 数据库的设计思路。
要实现前几天想好的,只保存差异信息的策略(经实测,可以减少 90% 的写 IO 操作),必须先统一数据读写服务的位置。不能依赖本地文件系统做数据交换。我之前考察过若干内存数据库,比如 Redis ,最终决定自己实现一个。因为我已经非常了解需求,可以高度定制算法,最大发挥硬件的能力。代码量也不会太大。(控制在 500 行 C 代码之内,最后实际写下来,不过 300 行 C 程序) |
【收藏】【打印】【进入论坛】 |
|
|
|
|
|
|
|