Node.js之网游服务器实践

2012 年 9 月 20 日

随着 Node.js 的不断发展与壮大,应用范围也越来越广泛,从传统的企业应用,到互联网使用,再到云计算的发展,它的身影也是随处可见。当然,它的受欢迎程度能在短时间内得到这么快的发展,除却与其本身的事件模型及 V8 的性能优化等一系列特性有关之外,还和国内外很多互联网公司的攻城师的大量应用和参与到开源项目中有密切关系,如网易的游戏开发,淘宝的数据之美等等。随着 HTML5 应用和移动互联网平台的指数增长,越来越多的用户使用了移动平台的休闲服务,采用 Node.js 实现高性能和可扩展性的游戏服务将是一件有意义的工作。

在互联网上,目前有一些采用 Node.js 实现的开源游戏服务框架,如 Mozilla 的 Browser Quest , Google 的 Grits Chilly 等。但是无一例外,这些框架不但与游戏逻辑联系紧密,而且几乎没有可扩展性和性能数据,同时也不提供任何游戏开发的管理工具,除了采用 JavaScript 编写外,很难体现出采用 Node.js 实现游戏开发的优越性。

概念

通常游戏分为角色扮演类和策略类及混合类等几种游戏类型。那么在网页游戏类型中,根据游戏的类型,开发者可能采用不同的架构实现方式,如策略类游戏可能更偏重于游戏的策略性和逻辑性,也就是考验游戏玩家的各种组合或搭配之类的游戏,对实时性的要求不会很高,在用户的可接受范围之内即可,因此也可以采用常用的 Web 应用开发模式来实现,而客户端采用轮询或长连接等方式来实现,这些应用模式也有很多的相关经验可以参考,因此能做到具有较好的可扩展性及伸缩性;此外,应用服务器和服务框架等也可以采用现存的技术实现,但是这只能满足对游戏实时性要求不高的场景服务,由于此类的应用方案非常成熟,在下文的讨论中,将不再详述。

角色扮演类游戏根据策划的要求可以实现各种各样的功能,如聊天,打斗,自动刷怪等各种复杂的功能,但是开发者基本可以把这些功能抽象地划分为二大类,即服务端的网络传输与逻辑运算能力,这样游戏服务的开发和实现会稍微简单些。现在,网络的实时传输基本上是采用 Socket 服务器来实现的,逻辑运算这个就不需要再述了,因此,游戏服务器就可以粗略的归纳为 Socket 的服务器综合游戏业务逻辑的实现。

与 Web 应用区别

从上面游戏服务的概念简单介绍中可以得知,实时游戏服务器的开发与传统的 Web 开发还是具有一些不同点,这些不同点着重体现在以下几个方面:

  1. 大部分的 Web 应用还是短连接的方式去实现,而游戏的实时性需要采用长连接的方式进行。
  2. Web 应用的场景一般是读多写少的应用,热点数据基本可以缓存;游戏服务的数据经常发生变动,如移动中的路径,打怪掉血等,缓存基本很快就失效,属于写多读少的应用场景。
  3. 广播特性,即 Web 应用行为上基本只需要用户与服务器交互,其他用户要实现共享他人信息的时候,会采用类似拉的方式如长轮询来实现数据交互,即使在没有数据更新的状态时也需要消耗一定的服务端资源,造成一定的浪费;游戏里面的交互方式需要实时进行,对影响三方的数据,必须采用广播的方法进行消息推送,即推的模式。
  4. 目前的 Web 应用开发一般采用无状态性的方式来实现,因此可以实现较好的可扩展性,很多 Web 服务会采用绑定会话或集中会话等方式工作,当后面的某台服务器出现宕机时,服务也可以很快的切换;但是游戏服务器和用户之间的连接是有状态的,在断开时需要进行相关的通知和数据持久化,在重连时需要进行状态恢复等手段来维护游戏世界的运行。

除以上几点不同之外,还有负载均衡的策略,服务驱动方式,系统判定,系统安全如外挂作弊等相关问题,文章中不再对他们进行对比说明。一般来讲,根据游戏的规模与需求,通常游戏服务器的实现的模式会使用如下的三种架构方式来实现。

单进程服务架构

图 1 单进程服务架构

图 1 是目前互联网上的很多游戏服务器使用的架构,比如商用的 SmartFoxServer 游戏服务器, 开源的有 Google 的 Grits , Mozilla 的 Browser Quest 等框架。所有的程序在同一个进程里面运行,架构简单,开发人员上手快,开发和调试非常方便又快速,成本较低,后期的维护成本可能随着游戏项目的大小而不同,但是整体应该不会太高。很明显,游戏的世界是在同一个进程里运行,在单个场景都有不少在线用户的时候,如果采用单进程的 Node.js 实现的话,每个用户的操作可能会有一定的延迟,特别是在大量用户同时做大量操作与 AI 运算时,如服务端寻路和自动杀怪,延迟会更加严重;同时单进程对计算机资源使用有限。因此只适合游戏的原型制作或小型的游戏开发。另外,由于游戏的状态与复杂性,也无法较好的实现游戏的可扩展性,基本只能通过添加游戏服务器的方式来实现,即所谓的开新服,而这些用户之间是无法在同一个世界里通信会话和交互的。尽管 Node.js 支持原生的 TCP 服务,使用简单;但是目前大部分网络应用中,尤其是支持 HTML5 的应用, socket.io 由于性能较好并且使用简单,成为大部分应用采用的网络库来实现高性能 websocket 服务。在移动互联网应用中,由于协议较复杂与通信数据等问题,需要一定的裁减。下面示例中,就是最简单的单进程服务的原型:

复制代码
var socketio = require('socket.io');
var io = socketio.listen(8080);
io.sockets.on('connection',function(socket){
socket.on('disconnect',function(){
//user leave
});
socket.on('message',function(msg){
if (getLoginStatus(socket)) {
game[data.action].apply(null,[socket,msg]);
} else {
game['login'].apply(null,[socket,msg]);
}
});
});

多进程服务架构

图 2 多进程服务架构

在上图 2 中,我们可以看出,每个进程负责采用单一职责,各进程间通过一定的规则(如 RPC 远程过程调用)来进行通信,游戏中的各场景服务使用一个进程来服务,实现各场景游戏运行的分离。但是在开发过程中,不需要考虑每个场景具体的信息,全部采用统一的代码来实现,开发方便,通过添加适当的参数来实现较易的调试,每个进程分别运行在服务器的多核上,即可以充分使用计算机的资源,也由于职责单一,由于每个进程都专职做自己的事,因此,在压力大的时候,可以很明显的查出问题所在,并可以较好的实现对相应的服务进行调优,可以达到较好的性能和一定的可伸缩性。但是,在各游戏的世界里,无法准确的知道游戏的状态如在线人数之类的,会存在热点数据和服务过载等一些问题,造成部分服务堵死等现象,也无法达到在不停服务的情况下进行服务器扩容与自动切换等问题,同时会给游戏运营带来一定的问题,不能充分的对玩家数据进行分析与挖掘,因此,这种实现方式可以满足中小型的游戏开发。这种架构目前很多的游戏服务器中还在使用,技术发展也较成熟,如传奇服务端架构等。

分布式服务架构

图 3 分布式的服务架构

同样,分布式的服务架构与多进程的架构具有很多相同点,如进程服务单一职责,较好的性能调优,同时采用集中式的方式对游戏服务器进行管理,各游戏服务器可以通过自定义的方式即 DSL 进行游戏业务的路由与分发,通过主备服务器的状态可以很清楚的知道每个服务的状态及运行情况,这样可通过动态添加服务来到达负载均衡,具备较好的性能和可扩展性,能满足大型游戏的开发。当然,采用这样方式,会具有一定的复杂性,同时各服务之间可能是不在同一台服务器上的,因此会带来一定的网络开销,在大量广播的场景中,网络 IO 压力大,需要通过定时批量或 AOI 服务等方式来进行广播通信,同时,游戏的通信协议需要经过特别的设计,尽量避免数据序列化上的 CPU 重复开销,采用胖客户端的方式去分担游戏服务器的序列化与反序列化;另外还有可能引入分布式事务等相关问题,需要在一个集中点进行处理。当然,在游戏设计中,策划开发人员需要尽量避免这种跨场景跨进程的游戏逻辑需求。

在分布式架构实现中,目前商业上较成功的案例是 BigWorld 游戏服务框架,国内外很多大型的游戏开发商都在使用。而在开源部分,目前还没有较稳定成熟的方案,我们团队采用 Node.js 正在开发的 Pomelo 游戏服务框架正在努力实现这一目标,现已进入最后的文档整理阶段,预计将在十月份进行开源与线上示例演示。

总结

采用 Node.js 来实现网络游戏服务器的开发,除了能利用 JavaScript 的动态语言的优势特性、强大的开源社区和 V8 引擎的优越性能外,在网络数据传输的异步化方面具有快速、实时等事件编程模型;特别在 HTML5 的应用开发中,还具有前后端代码共享、DSL 灵活定义等特点。但是也由于 JavaScript 的动态脚本语言的性质,同时 V8 在内存使用上的限制等问题。尽管 V8 引擎对脚本语言通过动态编译进行了静态化,但如果开发工程师在大规模开发时,没有注意到一些代码的细节,比如代码的额外异常检查和类属性的动态变化等方面,导致未经过调优的代码整体上还是无法和 C、Java 等静态语言相比的,然而在经过调优过后的代码是可以达到静态语言的性能的。在云计算应用中,也得到很多公司的推广与使用,如国外的 SmartOS,Vmware 以及国内的阿里云等,相信其性能与应用层面都会发展的越来越好;

此外,采用 Node.js 来实现游戏服务框架并开源也是一件非常有价值的事情,在追求高性能与低成本同时,希望能给开发者提供即简单又快速的开发方式,游戏开发者通过使用游戏框架,可以完全把精力放在业务代码的实现,不需要再把精力放在框架的实现。有机会我们将在后面的一系列讨论中介绍采用 Node.js 来开发游戏服务架构遇到的很多性能问题及优化过程。 最后,本文并不是建议大家一定要采用 Node.js 去开发游戏服务器,只是给新老游戏开发者提供一种解决方案,欢迎大家关注和探讨我们团队近期将开源的 Node.js 游戏服务解决框架。

个人介绍:

尧飘海,网易杭州开发工程师,系统分析员,正致力于 Node.js 的移动游戏引擎开发和性能优化等方面的工作,同时对 JVM 性能和服务端的应用架构设计也很有兴趣,欢迎交流。个人 Github 地址: http://github.com/piaohai


感谢贾国清对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2012 年 9 月 20 日 00:0024123

评论 1 条评论

发布
用户头像
先了解一下
2020 年 09 月 22 日 00:09
回复
没有更多评论了
发现更多内容

代码防御性编程的十条技巧

C语言与CPP编程

程序员 编程语言 C语言 编译器、程序语言、CPU

做好分库分表其实很难之一

架构师修行之路

微服务 分库分表

面试中常见的C语言与C++区别的问题

C语言与CPP编程

c++ 编程语言 面试题 C语言 编译器、程序语言、CPU

SpringBoot 异步任务

hepingfly

Java springboot 异步任务

字符串操作的全面总结

C语言与CPP编程

编程语言 C语言 编译器、程序语言、CPU 字符串

架构师训练营第二周命题作业

成长者

极客大学架构师训练营

架构师训练营 Week2 作业

lggl

极客大学架构师训练营 作业

一、搭建Python环境和安装Pycharm

刘润森

Python

二、搭建Jupyter Notebook环境

刘润森

Python

架构师训练营 1 期 -- 第二周作业

曾彪彪

极客大学架构师训练营

架构师训练营第 1 期 - 第二周 - 作业提交

Todd-Lee

架构师 极客大学架构师训练营

架构训练营 -week2- 学习总结

于成龙

面向对象 架构训练营

七、连Pycharm都不知道怎么用,学什么Python

刘润森

Python

九种查找算法

C语言与CPP编程

面试 算法 编程语言 C语言 编译器、程序语言、CPU

十大经典排序算法(动态演示+代码)

C语言与CPP编程

算法 编程语言 面试题 编译器、程序语言、CPU

八、给小白看的第一篇Python基础教程

刘润森

Python

高并发下如何缩短响应时间

架构师修行之路

微服务 高并发优化

架构1期第二周作业

FG佳

架构师训练营第一期 - week2 - 命题作业

谭明华

极客大学架构师训练营

前言、Python是真的火,还是炒得火?来看看它的前世和发展

刘润森

Python

C语言C++中assert的用法

C语言与CPP编程

程序员 编程语言 C语言

六、乘胜追击,将剩下的Git知识点搞定

刘润森

学生成绩管理系统案例

C语言与CPP编程

编程语言 C语言 编译器、程序语言、CPU

一文轻松理解内存对齐

C语言与CPP编程

程序员 编程语言 面试题 C语言 编译器、程序语言、CPU

五种简单高效的拆分用户故事的方法

Bruce Talk

敏捷 Agile 用户故事 User Story Product Owner

三、新手Jupyter不会用,我十招教你盘她

刘润森

Python

四、学编程语言前,不了解Git,怎么入坑

刘润森

Python

[架构师训练营第1期]第二周命题作业

猫切切切切切

极客大学架构师训练营

「架构师训练营第1期」第二周作业

张国荣

极客大学架构师训练营

五、开始Github和码云之旅,新手如何上路

刘润森

Python

十、给小白看的第三篇Python基础教程

刘润森

Python

Node.js之网游服务器实践-InfoQ