本文档主要是调研分析新的手游服务端架构,为下一款手游服务端研发提供可参考的方案。主要的参考点是数据持久化,并发效率,分布式,沙盒机制,热更新机制,研发成本。如果从头根据需求开发一款新的服务器架构,需要大量的时间,而且可能会得不偿失,一款新的架构稳定都需要时间的。我们应该尝试使用开源的,成熟的,活跃度高的开源框架。正文将先从服务器设计需要考虑的因素进行需求分析,以至明确我们需要的服务器架构原型,然后再分析现有的开源架构方案。
服务器操作系统大多采用Unix和Linux操作系统,而Linux发行版本系统中,多使用CentOS、Redhat、Ubuntu、Gentoo、Debian。而这些发行版本可以大体分为两类,一类是商业公司的发行版本,一类是社区组织的发行版本,前者以著名的Redhat(RHEL)为代表,后者以Debian为代表。Redhat的稳定性和硬件兼容性都比Debian高。并且RHEL的生命周期是7到10年,基本上可以覆盖硬件的生命周期,也就意味着一个新硬件安装以后,不用再次安装操作系统。而Debian的生命周期是不固定的,一般新版本发布以后,上个版本再18个月。而Debian的版本发布时间间隔不稳定,经常会延期。综合起来一个版本的生命周期一般在3~4年。如果选用了Debian或者Ubuntu作为服务器,等生命周期过了以后,就没有安全补丁,服务器就会有安全风险。
基于以上对比,在给服务器选择Linux操作系统时,我们会优先考虑Redhat系统的操作系统。
由于CentOS源于Red Hat企业级Linux(RHEL)的源代码,依照源代码释出的源代码所编译而成。由于CentOS开源特性,选择CentOS可以降低成本,同时又能够享受RHEL的服务支持。目前市场最大的两个centos系统是centos5(2007)和centos6(2011),最新的是centos7(2014),每一次的大版本升级系统意味着更好的稳定性和功能扩展。由于centos7刚刚在2014.7月发布,最新版本7.2稳定性还有待考证。所以选择centos6是最为合适的。Centos6版本最新centos6.7。
对于服务器而言,数据是最重要的。有一个好的数据持久化方案,对于服务器开发将会是事半功倍。在介绍数据的持久化方案以前,我们现在介绍服务器数据读取和修改的运用场景。服务器运行过程中会不断的生产数据(增删读改),并且将数据落地(如存入数据或者文件)。
但是如果服务器每次都直接从数据库或者文件读取和修改数据,那么服务器的io操作可能成为瓶颈,可以采用异步读写方案来降低io读写对服务器逻辑处理能力影响,但是同时提高逻辑的复杂度。 所以较好的方式是将数据缓存到内存中,同步读写,每次读写都先操作缓存,然后再由缓存同步到数据库。同步机制决定了数据持久化能力。如果自己去实现这套机制无疑是造轮子,而且效果不一定好。数据持久是每个服务器都需要的。已经有很多专业的团队提供有很多开源的成熟的解决方案。如memchache和redis缓存系统。
注意:对于在选择数据持久化方案时,需要考虑我们的对数据的需求,如是多读少写,还是少读多写。在游戏服务器中还应该考虑合服和跨服等需求是否方便实现。
对于游戏服务器而言,首先应该的就是高并发处理能力,从而最大限度的提高服务器的吞吐率,提高单服在线人数,降低服务器硬件成本。实现的方式有很多,没有决对好坏。只有针对具体的需求,才有好差之分。
好处:不用考虑锁,多线程数据同步等。开发门槛低,开发方式方式灵活,bug定位容易,新人上手容易
对于游戏服务器架构而言,服务中存在很多模块,所以的模块都将混入这个单线程中,如果cpu性能出现瓶颈, 优化成本将会非常高,由于是单线程,抛弃了锁等,给程序员提供太大的空间,往往在书写的过程中比较随意,如果没有沙盒机制,要代码的质量对参与开发的每个程序员的水平要求都要要比较高。
由于单核cpu的能力限,并且游戏服务器模块较多,并非处理某一类单一问题,所以服务器的实现应该尽量利用cpu的多核处理能力,而且可以绑定线程,提高cache命中概率。但是多线程并发编程如果没有一个好的系统机制或者语言支持,如使用c或者c++语言。门槛较高,要求开发人员的必须具有较高的架构能力,否则将会出现多线程处理能力不如单线程。所以如何降低并发编程的门槛也是服务器架构设计的重要部分。
服务器分布式,是指服务器支持分别部署在不同物理机上。可以以增加物理机的方式提高服务器的承载能力。所以好的服务器应当有一个灵活的扩展分布方案。所以如何让我们的服务器具有灵活的扩展能力,将是服务器架构中的重要部分。
沙盒机制:就是让代码在一个比较安全的受包含的中运行,即使代码出现错误,产生了叫恶劣的影响,其能力将被在沙盒中不会影响到沙盒外的。这样的机制可以提高服务器的安全性和稳定性,并且提高开发效率。所以如何让服务器架构具备沙盒能力将是服务器架构设计的重要部分。
如果服务器能够做到不停服,那么可以在不承受停服带来的损失的情况下,修复服务器缺陷。然而我们通常应的c语言,c++,java的语言都是不支持热更新。通常只有解释型的脚本语言才能支持人更新,还有函数编程语言也支持简单热更新,因为函数编程语言函数内无状态。
相信有很多满足要求的服务器架构,但是选择这些架构需要根据团队的自身能力。比如erlang语言,天生支持分布式,支持沙盒机制,由于本身是函数式编程语言,天生时候并发编程,简单热更新等机制。但是团队中没有一个会erlang,我们也不会选择erlang语言。因为我们耗不起从头学习erlang的时间。
现在有很多面向并发或者易于并发编程的语言,他们大多是一些小众语言,如go,erlang语言。很多游戏喜欢用erlang语言做服务端。Erlang是函数式编程语言,接近自然语言易于理解,并且天生支持并发编程,热更新。Erlang的核心概念是节点,一个节点就是一个的系统包含了地址空间和的进程集的完整虚拟机,这种设计基础让erlang语言天生具有分布式和沙盒机制。根据erlang的这些特性,无疑完全满足我们对服务器的要求,而且大大降低了门槛了,因为我们不在需要考虑并发,考虑分布式,考虑锁等。但是一般小众的诞生都是为了特地的解决某一类问题而诞生的。自然就有这种语言最适合的,和不适合做的事情。Erlang适合做非计算密集超大并发服务器,不适合做数值计算和业务逻辑非常复杂的系统。
通过的分析,erlang语言确实适合轻量级游戏的开发,而且开发门槛低,特别是对于没有经验和技术沉淀的小公司而言。但是我们团队都没有学过erlang语言,都没有学习过任何函数式编程语言,虽然erlang的门槛低,但是要用好,相信还是需要的时间的。这将会大大提高的我们的时间成本。所以对于我们选择erlang是不太合适的。但是erlang的思想是可以借鉴的。如果我们能用熟悉的语言实现erlang的思想,无疑常完美。那有没有一套这样现成的系统,答案是肯定的。Skynet(开源服务器引擎)正是研发者在使用erlang时出现瓶颈后使用c语言和lua语言模拟了erlang的机制,最终替换了erlang语言。并且经过日活跃用户50万的。具体细节后面具体章节再讨论。
能够站在巨人的肩膀上,利用前人的研究,是最快最有效的方式。开源的游戏服务器有很多,根据我们自身的因素,我们只能选择c/c++ lua相关的。这样的服务器引擎目前比较活跃的有两个:KBEngine(c++ + python), skynet(c + lua)
KBEngine是一款开源mmog服务端引擎, 使用统一协议能够轻松与前端对接,能轻松使用unity3d、ogre、cocos2d、html5等作为前端表现。
底层框架由c++编写, 逻辑层使用python(支持热更新), 开发者无需重复实现一些通用的底层服务端技术, 使开发者能够真正集中精力到游戏开发上来, 快速打造各种游戏。
KBEngine底层架构被设计为多进程分布式动态负载均衡方案, 理论上只需要不断扩展硬件就能够不断增加承载上限,单台机器的承载上限取决于游戏逻辑本身的复杂度。
skynet是云风编写的服务端底层管理框架,底层由C编写,配套lua作为脚本使用,可换python等其他脚本语言。
两者数据对比分析,发布时间相同,社区关注度kbengine更胜一筹,但是参与开源贡献和方面skynet更胜一筹,并且skynet已发布1.0版本。Kbengine目前没有成功的产品。为什么会出现这种情况呢, 第一:kbengine封装完整,代码量大,用户可以很简单快速的搭建游戏服务器,但是很难把握其底层整个架构,一旦出现问题很难,这就是为什么参与其开源贡献的人比较少, 而skynet至提高一个简单框架,代码量少,使用者很容易把握住,虽然没有kbengine那么方便,但是也提供更多选择的空间。第二:skynet成功的产品都主导研发skynet的简约公司的产品,而kbengine没有成功的产品,有可能是有产品用了没有宣传,还有可能是研发者并没有将引擎用于自己的项目,只是业余时间开发。
总之从两者来看,skynet更适合,skynet只提供简单框架,完全模拟erlang的思想,并且采用了我们熟悉的两者语言lua和c, 并且代码量少,容易把握。并且已有成功的产品和相对客观数据验证了skynet的健壮性。下面对skynet架构做具体分析。
Erlang的主要几个核心点:节点核心概念,分布式, 并发编程,actor模式,沙盒机制, 多进程实,热更新,开发简单。
Skynet核心概念也是节点,一般称之为服务,也使用actor模式,也提供分布式方案(harbor和cluster模式),多线程驱动, 与lua结合,利用lua天生的沙盒机制(state)和热更新机制,同时通过lua来降低开发门槛。简单说:skynet利用了操作系统线程+luaState + lua coroutine实现了简单的erlang。Erlang底层也是采用c实现的,只是封装的更加完善,没有接种其他的语言实现自己是的沙盒方式。
skynet是简约公司云风主导研发的轻量级服务端底层管理框架,底层由C编写,配套lua作为脚本使用,可换python等其他脚本语言。整个服务器逻辑层偏lua。底层C框架主要工作是通过模块注册管理服务,并协调服务之间的调用和通讯。
Skynet是设计是使用actor模式,全部工作线程以消息驱动。服务器初始化启动多个工作线程,一个网络线程,一个定时器线程,一个线程。用线程带动各个服务运行,如果是lua服务则需要一个lua state协助运行,在lua state可以启动多个coroutine协同工作。详细见其层次图。
由于skynet使用actor模式,使用消息驱动,那么全局就需要消息队列。Skynet的消息队列设计如下,每个服务有个消息队列,全局有一个全局的全局队列,每个服务启动后需要将自己的消息队列注册到全局队列中, 所有的工作线程依次轮询全局队列,以此调度各个服务执行自己的任务。
每一个lua服务都有一个lua state,由此形成服务器的沙盒机制。配合lua中协同开发模式,可以同时执行多个任务。 对于c层的异步接口,可以在lua层封装一层阻塞接口,然后交给coroutine去等待。等待有结果时再coroutine。Coroutine的阻塞并不会导致整个lua state的阻塞,所以整个工作线程还是在运行的,简单的说就是用同步写异步。如:lua socket
skynet提供了更具弹性的集群方案。它可以和master/slave共存。也就是说,你可以部署多组master/slave网络,然后再用cluster将它们联系起来。当然,比较简单的结构是,每个集群中每个节点都配置为单节点模式(将harbor id设置为0)。
要使用它之前,你需要编写一个cluster配置文件,配置集群内所有节点的名字和对应的端口。并将这个文件事先部署到所有节点,并写在Config中。
4.Redis主服务负责接收数据请求,并派发给从节点,从节点负责从各自连接的redis数据查询更改数据。用于数据库连接池
对于多服的实现并一定时因为单服的承担能力不够,有可能是运营需要,运营需要多个排行榜,以养更多的大R,如很多页游开了几百个服,其实是开了几百个排行榜。所以实现为服务架构支持灵活的多服部署方式和合服策略是很重要的,合服操作的是数据,所以要在数据标识设计时要为后期的合服做准备。下图是skynet多服部署图,采用使用skynet cluster模式。
本文来源于ipfs
网友评论 ()条 查看