需要有关MMORPG数据模型设计,数据库访问和无堆栈python的建议

tex*_*ian 8 python database python-stackless

我正在开发一款回合制休闲MMORPG游戏服务器.

处理网络,多线程,定时器,服务器间通信,主游戏循环等的低级引擎(不是我们编写的)是由C++编写的.高级游戏逻辑由Python编写.

我的问题是关于我们游戏中的数据模型设计.

首先,我们只是尝试在客户端登录时将播放器的所有数据加载到RAM和共享数据缓存服务器中,并安排定时器将数据定期刷新到数据缓存服务器中,数据缓存服务器将数据保存到数据库中.

但我们发现这种方法存在一些问题

1)需要立即保存或检查某些数据,例如任务进度,等级,物品和货币收益等.

2)根据游戏逻辑,有时我们需要查询一些离线玩家的数据.

3)一些全球游戏世界数据需要在不同的游戏实例之间共享,这些游戏实例可能在不同的主机上运行,​​或者在同一主机上的不同进程上运行.这是我们需要数据缓存服务器位于游戏逻辑服务器和数据库之间的主要原因.

4)玩家需要在游戏实例之间自由切换.

以下是我们过去遇到的困难:

1)所有数据访问操作都应该是异步的,以避免网络I/O阻塞主游戏逻辑线程.我们必须向数据库或缓存服务器发送消息,然后在回调函数中处理数据回复消息并继续进行游戏逻辑.编写一些需要与db多次交谈并且游戏逻辑分散在许多回调函数中的中等复杂游戏逻辑很快变得痛苦,这使得难以理解和维护.

2)ad-hoc数据缓存服务器使事情变得更加复杂,我们难以维护数据一致性并有效地更新/加载/刷新数据.

3)游戏中的数据查询效率低且繁琐,游戏逻辑需要查询许多信息,如库存,项目信息,化身状态等.还需要一些交易机制,例如,如果一步失败,整个操作应该回滚.我们尝试在RAM中设计一个好的数据模型系统,构建大量复杂的索引以简化大量的信息查询,增加事务支持等.我很快意识到我们正在构建的是一个内存数据库系统,我们正在重新发明轮子. ..

最后我转向无堆栈python,我们删除了缓存服务器.所有数据都保存在数据库中.游戏逻辑服务器直接查询数据库 使用无堆栈python的微任务和通道,我们可以以同步的方式编写游戏逻辑.它更容易编写和理解,生产力大大提高.

实际上,底层数据库访问也是异步的:一个客户端tasklet向另一个专用DB I/O工作线程发出请求,并且在一个通道上阻止了tasklet,但是整个主游戏逻辑没有被阻塞,其他客户端的tasklet将被安排并自由奔跑.当DB数据回复时,被阻止的tasklet将被唤醒并继续在'断点'上运行(继续?).

有了上面的设计,我有一些问题:

1)数据库访问比以前的缓存解决方案更频繁,数据库是否可以支持高频率的查询/更新操作?在不久的将来是否需要一些成熟的缓存解决方案,如redis,memcached?

2)我的设计有任何严重的缺陷吗?你们能给我一些更好的建议吗,特别是游戏中的数据管理模式.

任何建议将不胜感激,谢谢.

BRP*_*ock 6

我使用过一种MMO引擎,它以类似的方式运行.它是用Java编写的,但不是Python.

关于你的第一组要点:

1)异步数据库访问我们实际上走了另一条路线,并避免使用 "主游戏逻辑线程".所有游戏逻辑任务都被生成为新线程.与I/O相比,在本底噪声中完全丢失了线程创建和销毁的开销.这也保留了将每个"任务"作为一种相当简单的方法的语义,而不是令人抓狂的回调链,否则最终会出现这种情况(尽管仍有这种情况.)这也意味着所有游戏代码必须是并发,我们越来越依赖于带有时间戳的不可变数据对象.

2)ad-hoc缓存我们使用了很多WeakReference对象(我相信Python有类似的概念?),并且还使用了数据对象之间的分割,例如"Player"和"loader"(实际上是数据库访问)方法)例如"PlayerSQLLoader;"实例保存了一个指向其Loader的指针,而Loaders由一个全局"工厂"类调用,该类将处理缓存查找与网络或SQL加载.数据类中的每个"Setter"方法都会调用该方法changed,该方法是一个继承的样板文件myLoader.changed (this);

为了处理来自其他活动服务器的加载对象,我们使用了使用相同数据类的"代理"对象(再次,比如"播放器"),但我们关联的Loader类是一个网络代理(同步,但结束)千兆本地网络)在另一台服务器上更新该对象的"主"副本; 反过来,"主"副本会changed自称.

我们的SQL UPDATE逻辑有一个计时器.如果后端数据库UPDATE在最后($ n)秒内收到了一个对象(我们通常将其保持在5左右),它会将对象添加到"脏列表".后台计时器任务会定期唤醒并尝试将仍在"脏列表"上的任何对象异步刷新到数据库后端.

由于全局工厂将WeakReferences维护到所有内核对象,并且会在任何实时服务器上查找给定游戏对象的单个实例化副本,因此我们永远不会尝试实例化由单个DB记录支持的一个游戏对象的第二个副本因此,游戏的RAM内状态可能与其中的SQL图像一次最多5或10秒不同,这一点无关紧要.

我们的整个SQL系统在RAM(是的,大量的RAM)中运行,作为另一台服务器的镜像,该服务器勇敢地尝试写入光盘.(由于"老年",这台糟糕的机器平均每3-4个月烧掉一次RAID驱动器.RAID很好.)

值得注意的是,当从缓存中移除对象时,必须将对象刷新到数据库,例如由于超出缓存RAM容限.

3)内存数据库 ......我没有碰到这种精确的情况.我们确实有"类似事务"的逻辑,但这一切都发生在Java getter/setter的层面上.

而且,关于你的后几点:

1)是的,PostgreSQL和MySQL特别适合这一点,特别是当您使用数据库的RAMdisk镜像来尝试最小化实际的HDD磨损时.根据我的经验,MMO确实倾向于锤击数据库而不是严格必要的数据库.我们的"5秒规则"*专门用于避免必须"正确解决"问题.我们的每个人都会打电话changed.在我们的使用模式中,我们发现一个对象通常有1个字段已更改,然后在一段时间内没有活动,或者发生了更新的"风暴",其中许多字段连续更改.构建正确的事务(例如,通知对象它即将接受许多写入,并且应该等待片刻然后将其自身保存到DB)将涉及系统的更多计划,逻辑和主要重写; 所以,相反,我们绕过了这种情况.

2)嗯,上面有我的设计:-)

事实上,我目前正在开发的MMO引擎更多地依赖于内存中的SQL数据库,而且(我希望)这样做会更好一些.但是,该系统是使用实体 - 组件 - 系统模型构建的,而不是我上面描述的OOP模型.

如果您已经基于OOP模型,转换到ECS是一个相当模式的转变,如果您可以为您的目的使OOP工作,那么最好坚持您的团队已经知道的东西.

* - "5秒规则"是一种口语化的美国"民间信仰",在将食物丢弃在地板上之后,如果你在5秒内捡起它,它仍然可以吃掉.