服务器之间主动推,还是被动拉取
1. 被动拉取模式(Pull)
由于我们的游戏服务器和跨服服务器代码基本一致,所以只要能在跨服中获得游戏功能所要的数据,那么,就能完成任何原有的功能,并且改造成本基本为零,我们选择了被动拉取。
这里要提出一个概念:数据源的相对性。
提供数据方,C向B请求一份数据,B是C的数据源,B向A请求一份数据,A是B的数据源。
一个玩家跨服过去后,往游戏原服拉取数据的细节图如图:
玩家先跨服过去,loginCrossServer(LoginCrossServerReq),然后,在用到任意数据时(主角、技能、坐骑、装备、宠物等),反向同步请求各个系统的数据。
我们的实现如下图所示:

玩家在游戏本服,获取Role数据,通过RoleRepository.cacheLoad(long roleId),先从Cache里读取,没有,则调用访问器My[color=rgb(68, 68, 68) !important]
SQLDataAccessor.load(EntityMappingem, Serializable roleId, K id)从数据库读取数据。
玩家在跨服,获取Role数据,通过RoleRepository.cacheLoad(long roleId),先从Cache里读取,没有,则调用访问器NetworkDataAccessor.load(EntityMappingem, Serializable roleId, K id),通过RPC远程同步调用读取数据session.sendRPCWithReturn(),该方法的实现可以参考上述的RpcClient.sendWithReturn(),相类似。
关于被动拉取的优缺点介绍,在下文另有论述。总之,由于被动拉取的一些我们始料未及的缺陷存在,成为了我们服务器端开发部分功能的噩梦,从选择该模式时就埋下了一个天坑。
2. 主动推送模式(Push)
为了解决了上面碰到的一系列问题, 并且还能坚持最初的原则,我们做了如下几点优化:
如果玩家在本服,和调整前一样的处理流程,如果玩家在跨服,客户端请求的指令,发布的事件,异步事件需要在场景Stage线程处理的,就转发到跨服,需要在其他个人业务线程(bus),公共业务线程(public)处理的,仍旧在本服处理。
场景业务线程不再允许有DB操作。
内部指令的转发、事件分发系统、异步事件系统要在底层支持跨服。
玩家在登录本服时就会构PlayerTemplate, 场景用到的数据会实时更新,玩家去跨服,则会把场景中用到的数据PlayerTemplate主动推送给跨服。主动推送模式如下图所示。

看下事件分发代码的改造:

如下图,举个例子,在跨服怪物死亡后,会抛出 MonsterDeadEvent事件,在跨服进程直接处理场景的监听对应的逻辑:
场景中道具掉落,尸体处理;其他的监听逻辑抛回游戏服处理,根据这事件,任务模块处理完成任务,获得奖励;成就模块处理完成成就,获得奖励;主角模块获得经验,金币等奖励;活动模块处理完成活动,获得奖励。
其他方面的优化
1. 消息组播机制
消息组播的优化,在跨服,来自同一服的全部玩家广播从分别单独消息转发,改成一个消息发回本服,然后再广播给玩家(比如来自同一个服n个玩家,原本广播一条消息,服务器之间之间要处理n个RPC消息,现在只需要处理1个消息,降到了原先的1/n)。
2. 通信数据量
一个完整的PlayerTemplate模版数据由于包含了玩家在场景里用到的所有数据,比如角色、宠物、坐骑、装备、神器、法宝、时装、技能、翅膀等等, 数据量比较大,平均能达到5KB左右,需要在服务器之间传输时做zlib压缩,比如,做了压缩后,11767 Byte的玩家数据能压缩到2337Byte,压缩率可达到19.86%。
3. 序列化/反序列化
改造前,所有的请求都需要先在本服做AMF3反序列化,如果请求是需要转发到跨服的,再通过JSON序列化传输给跨服,在跨服通过JSON反序列化,最终该请求被处理。
但实际上,中间过程JSON序列化和反序列化似乎是没有必要的,经过改造,对需要转发给跨服的请求,在本服先不做AMF3反序列化,发送到跨服后再处理,这样就少了一次JSON的序列化和反序列化,同时收益了另外的一个好处:降低了传输的字节。
4. 内存占用优化
Oracle JVM目前只能在JVM停止运行的时候才能做到释放占有内存,直到下次重启,所以为了防止资源浪费,各种类型的跨服服务器,游戏服务器都需要设置不同的启动参数。启动参数的设定根据我们自行设置的公式,如图所示。
但内存占用仍然会经常突破预警线90%,由于一旦系统分配内存发现不够时,就会触发自我保护机制,进行OOM killer,所以需要预留很大的内存给每个JVM进程,而且每次维护的时候去脚本修改内存也比较麻烦。
内存占用状况如上图,服务器更新维护后,内存占用一路上扬,一直到最后维持在一定的值,不会回收,除非等下次维护或者系统触发OOM killer。
基于阿里 JVM 1.8,只要开启-XX:+DeallocateHeapPages,CMS能在不重启应用的情况下把不使用的HEAP归还给系统的物理内存,这样,我们就不需要预留很多空间给JVM,再也不用担心被误杀了。
拿我们一款内测阶段的游戏举例,使用了ALI JVM后, 64内存配置的机器最后开到了24个新区,相比起以前64G内存的机器,单台只能放9个独立的游戏区的状况下,单区的成本将会节省62.5% 机器资源,非常可观。完美的解决了内存经常吃紧的问题,并大幅节省了成本,这里特别感谢阿里云团队和@坤谷提供黑科技,大家有需求的话,可以去申请成为种子用户。
上图就是使用了Ali JDK后的锯齿形效果,每隔一定时间闲置内存会被系统回收,这在Oracle JVM是难以做到的。
5. 服务器分组机制
不定向跨服是指任意游戏区的玩家都有可能匹配到一起进行游戏玩法的体验,比如跨服战场,比如跨服副本匹配,如图所示(分别表示服务器分组前和分组后)。

如何在游戏正式大区中选择几个服做灰度服,又不影响不定向跨服体验,以及如何解决新老服玩家战力发展不在同一起跑线而导致的不平衡问题曾一度让人纠结。
比如游戏产品推出了大型资料片,想先做下灰度测试,让1~4区的玩家先做下新功能的体验,同时又能防止玩家穿了一件旧版本不存在的装备而在跨服环境下报异常,根据运营需求通过分组,就很完美的解决了上述问题。
6. 战区自动分配机制
定向跨服是指在一定时间内会固定参与跨服玩法的几个国家,常用于战区中国家之间对战,如图所示,需要运营在后台配置;当一段时间后,随着玩家流失,又需要运营根据战力进行战区的调整,对运营人员的要求比较高。
调整后,每一种基于战区的跨服类型都可以自定义调整时间间隔,到时间点全局服务器(global server)系统自动根据全区的活跃战力匹配进行调整,让运营人员从繁杂的配置中解脱出来。
7. 跨服断线重连机制
比如战场系统或组队副本,由于网络状况而掉线,如果重新登录后,没法进入,将会严重影响战场的战况,顺风局马上就可能会变成逆风局,主力DPS掉线副本就有可能通不了,这个机制就弥补了这块的缺陷。
支持的玩法
目前,我们已经能支持任意的游戏区玩家可以到任意的跨服服务器进行游戏功能的体验。比如已经实现的跨服组队副本、跨服战场、跨服国战、跨服皇城争夺、跨服资源战、虫群入侵战、跨服押镖、挖矿争夺等。
也支持玩家在本服就可以进行跨服互动,比如和别的区的玩家聊天、加好友、送礼等无缝交互,及国家拍卖行,世界拍卖行的跨服贸易。甚至支持玩家穿越到另外的游戏区做任意的游戏体验,比如一区的玩家听说二区服在举行抢亲活动,你可以跑到2区去观赏参与,也跑到任意的区的中央广场去显摆你的极品套装。
跨服在线数据
跨服定向玩法有战区国家玩法,虫群入侵,跨服押镖,挖矿争夺, 跨服皇城争夺,跨服国战等,如下图所示,我们可以看出这种玩法的规律:每次活动开启,跨服就会迎来一波波玩家涌入,活动一结束,玩家就会离开,4个跨服进程支持了7600在线的玩家。
跨服非定向性玩法有跨服组队副本,跨服战场等,支持负载均衡,可以随时动态增加跨服。这些玩法的规律是24小时随时可以体验进入,在线比较稳定,8个跨服进程支持了28000在线的玩家。
技术架构
下图为跨服通信拓扑图,属于整体架构的核心部分,关于这一部分的说明见图表:

关于整体架构的介绍,卖个关子,后续的文章会和大家分享。请大家关注聊聊架构公众号。另外此套架构历经了《大闹天宫OL》、《诸神黄昏》、《暴风王座》、《惊天动地》、《三打白骨精》、《英雄领主》、《封神霸业》等先后近两万组服务器运行的验证和考验。
作者介绍
江贵龙,游戏行业从业8年,历任多款游戏项目服务器主程,上海灵娱服务器负责人。 关注游戏服务器架构及优化,监控预警,智能运维,数据统计分析等。