MMO 游戏中使用多核
如果游戏中一个会话中的玩家数量只有数十人则服务器开发还算容易。我们可以用单线程模式开发游戏服务器,之后根据CPU内核数量运行游戏服务器进程就可以简单实现
但MMO游戏就不同,一个会话中会有数千名玩家。此时如果游戏服务器为单线程模式则使用1个CPU内核就会达到性能极限。
要解决这个问题1个MMO游戏服务器进程就要运行多个线程。
Windows服务器中可以用IOCP方便地开发thread pooling。但Linux要用epoll,而epoll是适合单线程的API。因此很难实现thread pooling。
这个问题我们也解决了。尽管是Linux但还是用了thread pool并在每个线程李都有epoll对象。并且每个epoll都配有socket。
不管用epoll还是IOCP,游戏服务器中消耗较多时间的是socket API的send函数。MMO游戏服务器的逻辑处理时间较短,但send函数会用较长时间。
使用Proudnet的IP摄像头 ‘Forview Pixie’
这个问题我们也解决了。方法是在thread pool中调用send函数。MMO游戏服务器中会经常向多个Client同时发送player或NPC人物的action。这个部分也会成为性能瓶颈。
能快速处理这个部分的方法就是减少访问Socket的message queue过程中发生的lock次数。当然这个lock时间很短所以也可以用spin lock。但无论是spin lock还是 mutex只要发生contention性能就会受到严重的影响。为了解决这个问题我们开发了low context-switch loop算法。low context-switch loop中会尝试各种socket message queue的try-lock,如果try-lock失败会直接跳过。接着access剩余所有message queue之后在整合跳过的message queue重新try-lock。
用这个方式我们获得了很好的成果。
这个算法非常有效。尽管MMO游戏服务器是单线程的且服务器的逻辑运算量较少,但解决了异步socket API用时较长的问题。
因这个特点,很多MMORPG都用了Proudnet。
在中国经历过的新鲜事
在韩国玩中国游戏或在中国玩韩国游戏的情况并不多,但还是有一些。我们在其中发现了一个很奇怪的现象。
左侧的设备在中国,右侧的设备在韩国。在左侧设备中发送ABC, ACD, AEF的消息。在右侧设备中接收这些信息,但让人惊讶的是收到的消息里还有左侧设备未曾发送的消息。
我们用了数据包分析工具。甚至在数据包分析工具中也发现了未曾发送过的数据包。当然在发送端的数据包分析工具中只显示正常发送的消息。
这是为什么呢?是被黑了吗?
正确的原因我们现在还不知道。希望有人看到这个文章后能告诉我们是什么原因。我大胆猜测有可能是中国的国家防火墙上是否有一些操作。
我们解决这个问题的方法是每次都要改一下数据包头的内容。
利用这个方法发送端和接收端的数据就一致了。
初次之外我们还发现和解决了很多问题。我们已经解决了PMTU discovery fail的部分网络环境中的通讯故障问题,MMO游戏中与多名玩家进行P2P连接和断开的反复过程中所发生的连接问题等。
同时使用基于TCP实现C/S networking、UDP和P2P开发游戏难度非常大。但得到的也很多,用这种方式就可以精确处理游戏中人物敏捷的动作。
使用Proudnet的PC网络游戏洛奇英雄传
分布式服务器和数据库cache
商用化网络游戏中只用一台游戏服务器的情况几乎没有。
因为我们不知道并发连接数会达到多少。
为此我们要在服务器引擎上创建多个分布式处理服务器以便处理大量并发连接。
网络游戏中游戏服务器做的最多的事情是加载和保存玩家信息。大部分情况下会存储到数据库中,但游戏服务器开发人员不喜欢去操作数据库。
开发游戏服务器时编写数据库查询语句其实是很烦的意见事情,而且如果数据库弄不好就会大幅降低服务器性能。
使用Proudnet的手机FPS游戏 Final shot
我们推出Proudnet的时候游戏服务器开发者们告诉我们这些内容,于是我们开始关注游戏服务器开发者们会以什么样的模式去访问数据库。幸运的是我们开发游戏服务器的经历较长所以很容易就找到了这个模式。
游戏服务器开发者访问数据库的典型的类型就是write,类似于套装游戏里将玩家数据频繁写入磁盘。游戏开发者不喜欢多处理器编程,他们希望玩家数据在数据库中的存储应该是异步处理的。
我们了解到这点之后提供了单方向数据访问(unilateral data access) API。这个功能我们称之为Database cache。
游戏服务器开发者只要在游戏服务器中调用我们提供的函数即可,函数的变量中就可以放入”哪个玩家的哪个工具变成了什么工具”等内容。我们的函数是异步执行的,所以函数会立即返回结果。因函数的返回速度快所以开发人员也不会出现线程的错误。如果没有这些,开发者的一个失误就可以导致并发连接的剧增和服务器处理速度的骤降。
但这些也并不充分,使用这些功能的游戏企业又找到了新的问题。我们刚开始实现的功能只能支持一台游戏服务器访问玩家John的信息,但游戏开发者们希望在其他的服务器上也要访问John的信息。
上图中左上角是游戏服务器,左下角是工具交易服务器,中间是DB cache,右上角是DB。
玩家John的数据在游戏服务器,他的最新状态缓存在游戏服务器内存中,我们需要实现的是右下道具交易服务器能安全地访问游戏服务器中John的数据。
为此我们又开发了非独占访问(non-exclusive access) API。使用非独占访问AIP,则其他的服务器就可以间接访问内存中缓存的数据。我们开发的非独占访问API类似于多处理器编程中使用的atomic operation。