现在的位置: 首页 > 综合 > 正文

利用共享内存来恢复玩家数据

2013年05月27日 ⁄ 综合 ⁄ 共 1922字 ⁄ 字号 评论关闭

 

听上去共享内存和游戏不太沾边,但在游戏服务器上利用共享内存这个技术已经不是一个新鲜的事了,畅游和像素早在天龙八部和寻仙就实现了这一应用(但不一定都像下面提到的这么用)。

较早之前和同事讨论过如何在现有框架里利用共享内存这种进程间访问统一数据的特点,最理想的应用方案是切合目前以场景为单位的逻辑进程分割方式,能访问同一个内存数据就意味着场景间的物体进出进程间完全可以不用传递数据就能完成,怪物跟着玩家跑到另一个场景也成为很自然的事情,由于进程间使用的对象数据始终只有一份,无缝大世界里的冗余单位的管理问题也就不存在了(atlas的解决方案是用中心抉择服务器来抉择大世界重叠逻辑区里冗余单位的数据归哪一个进程管理)。另一个应用是数据和逻辑分离的方案,这意味着玩家数据从数据库加载上来就放一个地方,服务进程谁取谁用,比如聊天服务器需要玩家名字时去共享内存取,拍卖服务器需要操作金钱了去共享内存取,邮件服务器要处理一个邮件物品的提取了还是访问的共享内存。功能进程之间的处理不会因为冗余的数据而产生脏信息,一个功能逻辑需要几个进程间协调时也显得相对容易些,再来可以实现一个进程来维护这个共享内存比如定时存盘,定时或延时改变某个数据。

这两个方案听上去很吸引人但都没成型,原因很简单:共享内存是一台机器上能访问的,而之前设计的多进程处理不同逻辑的目的就是为了让更多的机器能够分担运算,如果想用到共享内存意味着多个进程都必须放一台机器上,但样一来进程的优势就完全比不上线程了,线程间简单的内存锁访问就能解决的问题干嘛还非得动用到进程间的共享内存。

最近新加入个原畅游的程序介绍他们天龙的共享内存才发现其实还有另一种简单但确实已用于实战的方案:玩家数据备份。在处理进程崩溃时可能产生的玩家数据回档上,通用的就是采取n分钟自动存盘一次的方法,但这个方法一来不可能把频率提得很高,二来即便提高频率了也不能保证玩家重要的信息不在存盘前产生。百多号人的程序团队很难保证程序在后期新功能上线时的能够快速稳定,这时候保证玩家的信息不被频繁回档成为优先解决问题。共享内存正好在很大程度上能解决这个问题,只需要一个维护进程负责在逻辑进程崩溃的时候保留这块共享内存(共享内存需要超过1的进程引用才能保证不被系统回收),能保证在下次启动逻辑进程前处理完存盘就行。天龙的共享内存策略核心是利用数据冗余数据来保证备份,可以把共享内存的应用简单理解成是进程专属的小型数据库,实际在逻辑进程里依然有一份玩家数据的本地内存信息,在逻辑中需要修改玩家信息时在更改本进程内存的同时更新共享内存的数据,另外一个维护进程则负责数据加载和定时往数据库存盘的工作,维护进程和逻辑进程间由于要操作同一个对象的信息所以访问共享内存时都需要加解锁操作(网上也能搜到这套方案的设计文档,呵呵) 这套策略确实简单好用。

基于备份玩家数据这一目的,适合目前项目的方案也清晰了:维护进程就只管进入共享内存不访问任何数据,逻辑进程启动的时候负责遍历已有的共享内存区域,如果里面存在数据则判断数据完整性后存盘,存盘结束以后释放共享区域。这样一来访问共享内存也不用加锁解锁,维护进程只要启动之后就一直pause住就行了也不必担心会不会有什么操作能导致它的崩溃;再来由于访问没有互斥也不需要另外操作一份本地的内存来保证速度,直接操作共享内存里的数据就行(目前的测试结果表明操作共享内存和操作本地内存效率一样,1kw次下同为16ms),而且恢复也不用特殊写,逻辑进程本身就有存盘逻辑。幸运的是现有逻辑进程里用的属性系统和这套方案对接起来也很容易,原有的设计目标就是把任何一个场景对象能用到的可以被关心的数据(这类数据只包括策划案里提到的对象属性而不包括程序运行时产生的那些,这些也正好是需要存盘的)统一放一块连续内存中管理,按属性注册的类型和索引去访问和修改。由于不是所有对象都需要共享内存的备份,能用到的只有和玩家相关的对象,比如玩家,BUFF,物品,公会等等这一类的,在属性系统里分配内存时需要提供2allocator;一种基于本地内存池,一种基于共享的内存池。更加幸运的是这些allocator ACE 已经提供给我们了,而且还不止这2种还外带移植linux的可能。(具体实现方法见下一篇。)

按这种方案实现的版本目前运行良好,不过有个意外比较纠结,如果有2个不同的工程生成的exe要操作这块内存会出现读写错误。。。比如维护进程和逻辑进程都访问这块内存,后访问的就会有问题。但有同一个工程生成的比如多个逻辑进程要访问这块内存却没这个问题。 这2个工程设置和内存对齐模式也都一样的。。。好在目前看来维护进程的是用不到访问这块内存的。

 

抱歉!评论已关闭.