H2Engine服务器引擎介绍

H2Engine服务器引擎介绍

简介

  H2Engine服务器引擎架构是轻量级的,与其说是引擎,个人觉得称之为平台更为合适。因为它封装的功能非常精简,但是提供了非常简洁方便的扩展机制,使得可以用C++、python、lua、js、php来开发具体的服务器功能。H2引擎的灵感来源于web服务器Apache。大家都知道Apache封装了浏览器的的连接和协议通讯,而具体功能逻辑则通过fastcgi的方式交由不同的编程语言实现,本人大学的刚接触php的时候,看到在php里print的字符串直接就出现在浏览器里,当时的感觉就是哇!这接口设计的真是帅!因为每个程序员最先学会的就是print,就会感觉这个接口设计的真是简单易用。所以php真是当之无愧的最好的编程语言(哈哈)。后来一直从事游戏服务器开发,发现在服务器引擎领域就一直没有这种Apache类似的设计非常通用、易理解、易扩展的引擎。现在游戏服务器领域大部分项目都是各搞各的,每个主程各搞一套自己用的舒服的架构。有些大厂或者相关的公司开源了一些服务器引擎,乍一看特别吊,但是跟Apache+php的这种架构相比,其易用性难以望其项背。当然服务器的长连接模式比web的request/response的模式本质上有更大的复杂性,服务器引擎的设计难点主要有如下几点。

  1. 通讯协议没有标准。大家都知道,http有行业标准,所有浏览器都是按照标准与服务器通信的,而通信部分的实现是服务器最为关键的部分,服务器程序员一般都知道,《网络编程》没看过几遍是写不了服务器程序的。一般而言服务器会采用二进制通信,常见的组包格式有2字节协议号+2字节标记+4字节包体长度+包体数据,这种协议格式紧凑,2字节的标记留作扩展也比较够用,比如是否启用压缩、加密等,但是这种对某些编程语言不是很友好,比如js就无法采用此种协议。
  2. 消息封包没有标准。消息封包常见的有struct二进制、自研的序列化、pb、thrift、json等几种形式,而在web领域,一般要不json要不xml。在服务器领域一般采用pb的较多。
  3. 编程语言多样。服务器编程语言为了高效,总体以c++为主,但是java、c#、python、lua、php、js也越来越流行,尤其是c++嵌入lua的模式大行其道。让服务器引擎像Apache一样可以支持各种语言,实现上很有难度。
  4. 并发与异步。通常游戏服务器为了平衡游戏复杂性和性能,采用多进程且每个进程主逻辑单线程的方案,多进程增加吞吐,单线程的程序更好保证稳定性,为了主逻辑不阻塞,几所所有的io操作都是异步完成的,这与Apache的理念有很大的区别,这使得Apache引擎很难封装的像Apache那样简洁,市面上有些人尝试了用协程简化异步,但是目前还形成相对成熟的方案。
  5. 数据同步的复杂性。Apache中php也是多进程的,但是不共享数据,无状态的php设计本身就大大降低了复杂性,但是长连接是有状态的。php中把状态数据放到memcahe、redis等内存数据库中,游戏服务器的多进程架构中也难免有数据需要共享,比如行会数据,但是像php那种通过分布式内存数据库同步方式获取在性能上(比如实时rpg游戏)是无法忍受的。如果采用异步获取,逻辑代码势必支离破碎,到处都是回调,难以维护。通常的解决方案是单独拎出来一个进程处理共享数据,比如CenterServer处理行会请求,所有行会操作都会转到CenterServer处理,再将结果同步到其他进程,这样不存在数据竞争和同步的性能问题,但是逻辑因为异步仍然是复杂了特别多。
  6. 性能难以量化。大家都知道Apache提供了ab程序可以量化服务器的性能,在服务器领域几乎没有通用的量化工具。一般都是会上线前用机器人压力测试一下,不能很好的量化各个接口的性能,web领域对接口性能量化的工具比较多,很成熟,确实值得研究学习,因为优化的原则就是现有数据再优化,必须知道哪些需要优化,优化完有多少效果。

  那么如何解决以上问题呢?经过闭关苦思七七四十九天,终于有所开悟,继而设计出来了H2Engine服务器引擎。接下来本文将阐述H2架构的设计细节,以及是如何演化得来。

H2Engine服务器引擎的演化

  先看下最为常见的游戏服务器架构图:

   这个架构是很成熟的,同时充分考虑了系统可伸缩性。Gate和GameServer是性能的关键,这两个都可以平行扩展,H2引擎就是从这个架构抽象而来。首先看Gate这个组件,每个Client连接一个Gate,而GameServer具体有多少个是对client透明的。因为可以启动N个Gate,所以这个架构理论上可以支持N个Client。linux实现的Gate单个进程撑2万连接已经不是问题,但是对于分服方式的RPG游戏,有哪个能做到单服在线2万的?我们的游戏都是限6000在线上限,超过就得排队了。主要是怕后边GameServer太卡,因为玩家有聚集效应,都会集中在比较热门的地图上。所以当今linux epoll单机如此高性能的基础上,单个gate进程玩家就足够应付一个区服的Client连接。所以在上面的架构图中简化为单gate,如下图:

   这个时候发现LoginServer的功能就有些鸡肋了。LoginServer本来是类似于DNS的功能,它会返回负载最小的Gate给Client,从而保证Gate的负载均衡,但是现在已经单Gate了,LoginServer变得不是很有必要了,原来的LoginServer上的账户验证功能完全移植到GameServer来做。所以在H2引擎架构中,不再有LoginServer的角色。

   Gate和GameServer肯定是不能少的了。DB是不是是必须的组件呢?答案是否定的。如果从DBServer发展的历史来看,当DBServer出现的时候,内存数据库还没有兴起,如今,Memcache、Redis等内存数据库已经大行其道,无论从效率还是稳定性,或者灵活性上,都更值得推荐。从运维角度讲,他们维护通用的内存数据库也更有经验。但是就本人看来,大部分情况下连Memcache、Redis这种都不需要,直接GameServer缓存一下就行了(主要是处理下断线重连,手游闪断还是很频繁的),因为GameServer本身就是有状态的服务器, 从上线后玩家数据就已经载入内存了,相当于所有的读操作都是缓存好的,所有的更新操作直接写数据库理论上完全可以撑住,而且直接写数据库也避免了小回档问题。因为毕竟写操作对于读操作量级小太多。如果真的应用场景需要缓存数据,那么部署一个Redis吧。去掉了DBServer,H2引擎架构简化成了只有Gate和GameServer,这次真的简化到极限了。

   下面让我们来讨论N个GameServer应该放几台机器上的问题。标准答案当然是需要几台放几台,但是如果你身边有运维的话,他可能给出的答案是一台机器,为什么呢,原因其一是这样运维更方便管理,下发程序、配置、重启、监控等也更容易。原因其二是现在机器都是多核cpu,内存也是过剩的,单台机器的处理能力与往日不可同日耳语。GameServer是主逻辑单线程的,如果一台机器上部署一个,那么cpu资源无法得到更好的利用。就本人经验而言,GameServer很少需要超过4个,为啥?想想看,如果一个RPG游戏单服设计在线1万人,平均分配到每个进程也就是2500人,很轻松啊,当然如果人过多聚集在单个进程,那还有有可能单个GameServer成为瓶颈,这种情况多开GameServer也解决不了问题。从cpu利用上来说,GameServer主逻辑单线程只能用一个cpu内核,考虑到启停io线程的计算需要一个cpu的计算量,那么平均2个cpu,4个GameServer也就是8个cpu,现在服务器没有8核好意思说是服务器?以往经验来看,玩家会比较集中在热点地图,一般会某个或某两个GameServer相对会cpu较高。另外一个服务器角色Gate是io密集型的,所以和GameServer放到一个机器上,也是扛得住的。这样在H2引擎中,完全有理由将进程全部跑在一个机器上,先上一个架构图,然后再讲一下这样设计有何特点。

   到这里大家有没有发现,跑在一台物理机的Gate和GameServer像不像Apache和php的关系?到此,H2引擎的雏形已经形成。Gate在这里扮演Apache的角色,GameServer在这里就是php的角色,Apache有一层fastcgi的东西实现进程间通信,只要按照fastcgi的标准,就可以让Apache支持任何的编程语言,在H2引擎中,也设计了一套进程间通信机制ffrpc,区别于Apache的fastcgi,ffrpc是基于消息+回调机制的长连接通信方式。ffrpc的实现暂时不展开了,现在H2引擎里已经实现了c++、python、lua的支持。H2的雏形已经有了,还需要进一步的抽象完善,因为H2不仅可以用于游戏服务器,在实时聊天、消息推送等需要长连接的应用场景也可以适用。所以为了更加容易理解,对Gate和GameServer组件的名称进行重新命名,变得更加通用一些。

   前边讲到服务器引擎设计的6大难题,下面讨论下在H2引擎中是如何解决的。首先是通信问题,Apache通用是因为Client都是用http协议,那么可不可以让游戏服务器的Client统一用某种通信协议呢?坦白说太难了。但是本人认为,随着websocket的逐渐普及,websocket可能有一统江湖的可能。其实有了websocket大家自己设计通信协议的理由已经很小了。H2集成了两种通信协议,websocket和普通的二进制协议,如果你的Client已经使用了websocket,那么接入H2就是so easy了。

   对于问题2数据封包的处理,H2给出的答案就是无为而治,既然没有标准,那么H2也不干涉你的选择自由,交给H2Worker处理,数据封包对于H2引擎是透明的,但是建议大家使用pb或者thrift就好了,H2的ffrpc就是使用了thrift完成的进程间通信。本人更推荐thrift,因为thrift对于各个语言的支持更好,对于js这种处理二进制尴尬的语言都兼容的很好。

   问题3的多语言问题,H2设计了ffrpc库,每个语言只需要接入并实现几个简单接口就可以了,相当于每个语言都需要开发自己专用的H2Worker,比如H2WorkerPhp、H2WorkerPython、H2WorkerLua等,目前C++、Python、Lua、js、php的Worker实现已经集成到H2Engine中,也就是说如果你想用lua或者python来写游戏服务器,那你直接写脚本就可以了。H2Engine晚些会加入支持的语言是C#。

   问题4并发与异步的问题,H2Engine的设计是主逻辑单线程,提供一个IO线程池,IO操作用异步+回调的方式完成。其实IO操作主要就是数据库操作,IO线程会创建一个异步IO句柄,每个IO句柄投递的IO异步操作都是串行保证顺序的,所以IO线程池既能够保证多线程并发,又能够保证比如针对某个User的操作是顺序的、可靠的。

   问题6性能量化的问题,由于客户端的请求通过引擎被处理,那么H2Worker上就可以收集到所有接口的性能数据,统计后格式化定时输出,这样就可以量化各个接口的的性能。甚至可以开发出图形化展示工具,可以看接口性能随时间的变化,或者不同接口间性能的比较。

   最后着重讨论问题5数据共享的问题。前边提到ffrpc提供了基于TCP进程间通信的机制,对于单机还是多机,都是无差别的,那么H2Engine和H2Worker理论上放不同机器也是可以的。事实也的确如此,H2引擎其实对于多机是完美支持的,但是为什么将H2的架构限制在同机器呢,这主要是考虑到数据共享的需求,同机情况下,H2Engine和H2Worker就可以通过共享内存共享数据,其效率和便捷性与多机tcp模式不可同日而语。经过权衡,要比较优雅的实现进程间共享数据,限制在同机可以大大的降低复杂性,虽然牺牲了一些可伸缩性。

  首先SharedMemory并不存储共享的数据,只存需要更新的数据,相当于共享内存作为交换数据的媒介。进程间共享数据的流程如下:

  1. 每个H2Worker维护一个自己的ShareMemDataSet,在共享内存中创建一个信号量,并且单独开一个线程,监听在此信号量上,如果被触发,则立即从共享内存拷贝要更新的数据到自己的进程,并投递给主逻辑线程去更新SharedMemDataSet。由于ShareMemDataSet是主逻辑维护的,这样的好处就是主逻辑线程如果只是读取而不修改,那么直接使用本线程的SharedMemDataSet数据,性能自然是杠杠的,比如行会数据一般读取操作远大于写操作。
  2. 如果H2Worker要修改共享数据,他就要获取共享内存上的全局锁,然后拷贝要更新的数据到共享内存,然后唤醒其他H2Worker的信号量,待所有数据被拷贝完毕后,解除全局锁,因为更新操作一定是主逻辑操作的,所以获取完全局锁后,主逻辑会自动检查一下本地要更新的操作是否全部完成,保证加锁完毕后,当前进程的SharedMemDataSet一定是最新的。下面来一段模拟行会操作的伪代码:

  这种数据同步有多个好处,首先是数据竞争,共享内存加锁同步数据,效率非常高,使得加锁的粒度较小,避免多进程锁竞争。其二是更新操作很像发送消息,区别于异步发送消息的机制是,消息发送完,其他worker的数据立即得到了更新,这是异步消息发送机制不能比拟的。

总结

  1. H2引擎集成了websocket,也推荐大家在长连接应用中,逐渐使用websocket。
  2. 协议的封包pb、thrift已经很够用了,H2引擎支持pb、thrift、json以及传统二进制struct,但是推荐thrift,主要是效率和多语言支持都更好。
  3. 基于网游服务器的场景,H2引擎考虑到单台物理机的处理能力当前足以应付单服的需求,所以将H2的架构设计为部署在同机上,这样大大简化了服务器的架构,多gate的架构其实来源于rpg刚兴起的年代,那时候服务器的内存有限,cpu多核也还没流行,但是今非昔比,单机模式也就是伪分布式模式其实更符合实际。
  4. 针对传统网游服务器架构中多进程数据共享的痛点,H2做了特殊的设计,由于H2Worker在同一台机器上,得以使H2可以通过共享内存共享数据。   大家知道,Apache+php之所以在web领域里流行,还有很大一个原因是php的框架又多又好用,相比而言,网游服务器领域的引擎、框架都太落后了,主要原因还是服务器没有形成标准,这也是本人从业多年,孜孜不倦想要有所突破的地方。从web的成熟经验来看,功能开发的快,就要有好多框架,要有好的框架,就要有成熟标准的引擎,现在市面上有些游戏服务器引擎就经常会糅合引擎和框架的功能,有的甚至夹杂了游戏服务器的数据结构和游戏逻辑。H2的设计哲学,引擎的归引擎,框架的归框架,虽然跟Apache相比距离“引擎”的称号相距甚远,但是这是H2的目标。另外,基于H2的框架也会不断的增加完善。举个例子,针对rpg游戏,我们可以设计出一套c++的框架,比如封装地图管理、角色管理、道具管理、任务系统、成就系统、副本系统、npc系统等,想想看,2d rpg领域相关的系统还是很好抽象的。问题是没有标准的、成熟的引擎作为基础。相关从业人员应该有共鸣,比如A团队开发一套任务系统,给B团队也是用不了啊,大家的定时器、数据库接口都不一样,无法做到拿来就用。如果大家都用H2,别人开源的系统分分钟就可以拿来用,想象下还是挺美好的。不同的游戏类型框架实现是不一样的,不同语言实现细节也会不同,使用H2引擎后可以根据不同游戏类型、不同语言分类框架,这个是后续扩展H2引擎的计划。

相关连接

  1. 文档 http://h2cloud.org
  2. 源码 https://github.com/fanchy/h2engine

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏互联网杂技

产品容错性设计原则

随着互联网的飞速发展,越来越多产品尤其是2C类产品更加注重用户体验,其中错误对用户体验的影响是灾难性的,在此我总结出一些容错性设计原则供大家参考和探讨。 ? 一...

4559
来自专栏互扯程序

IaaS,PaaS和SaaS,QPS,RT和TPS,PV,UV和IP到底是什么意思?

今天我们来讲讲什么是云服务,云计算的三种服务模式有哪三种,我们经常评估服务的性能指标都有哪些,分别是什么意思,平时“那些人”说的QPS是什么,TP是什么,日活又...

1423
来自专栏Golang语言社区

优酷、YouTube、Twitter及JustinTV几个视频网站的架构

优酷视频网站架构 一、网站基本数据概览 据2010年统计,优酷网日均独立访问人数(uv)达到了8900万,日均访问量(pv)更是达到了17亿,优酷凭借这一数据成...

1.2K7
来自专栏媒矿工厂

SDI向IP过渡中的标准化

伴随业务的发展、新媒体的不断拓展、高清化网络制播的发展,广播电视中心从节目制作播出到节目传输中的以SDI设备为基础的技术架构,已难以满足未来技术和业务扩展的发展...

1662
来自专栏机器学习算法与Python学习

最新Python学习项目Top10!

过去一个月里,我们对近1000个Python 学习项目进行了排名,并挑选出热度前10的项目。这份清单涵盖了包括Web App, Geospatial Data,...

912
来自专栏黑白安全

Facebook 开源 oomd,一种处理内存溢出的新方法

近日,在 Facebook 的网站上,该公司的 Daniel Xu 宣布在 GPLv2 许可证下开源 oomd。oomd 是用户空间内存溢出杀手(OOM Kil...

612
来自专栏腾讯技术工程官方号的专栏

昨天,腾讯百万节点规模管控系统(TSC)诞生了!

10.5K6
来自专栏Rainbond开源「容器云平台」

微服务的误读与误解

1425
来自专栏Java架构

干货 | 携程图片服务架构一、服务架构二、 小结

1885
来自专栏北京马哥教育

专为设计师而写的GitHub快速入门教程

在互联网行业工作的想必都多多少少听说过GitHub的大名,除了是最大的开源项目托管平台,许多企业也都是用GitHub来协同开发工作,当然我们彩程也是其中之一。笔...

3716

扫码关注云+社区