关于社交游戏中的同步服务器(长连接服务器)

小菜鸟 2012-09-03

博客好久没打理了,今天有时间,把在公司wiki上写的一些东西移植一下吧

我们的游戏至今已经上线第四个同步玩法了

有点想法,也有些疑惑,在这里发一下,欢迎各位tx各抒己见

准确点说,这里所谓的“同步服务器”其实包括两个部分的内容,第一是说明通信方式是长连接,第二是服务器不具有动态扩容的能力,也就是说想增加用户数不能通过增加机器的方式来做

当然,同步服务器按道理说只包含第一部分的内容,但在这里算是一个特殊的语境吧

1.区别(主要区别于普通的web服务器)

a.通信模式很显然的,不多说了

b.冲突的解决方式

(这里的特殊语境,是指做游戏常用的数据库:腾讯的cmem,redis)

现在我所知道的冲突解决方案有这么几种

一致性缓存锁:这种锁的劣势是没有普通锁的notify机制,也就是说,必须事先加锁,锁失败了只能退出,一些在逻辑中间的事务性操作用这种方式就不能实现了

一致性缓存cas:cas是cpu操作原语,也就是compareandswap,通常在一致性缓存中是基于乐观锁的方式(对比版本号)来实现的

redis作者曾说了这种方式性能是非常不好的,但是如果我们在程序逻辑做一些优化,也未尝不可

普通锁:这种锁也不好:很不优雅,必须将逻辑框在一个个的tryfinally中,而且容易出错,而且一旦在逻辑中作为一个常态出现了,那么就不得不把整个逻辑当做了一个巨大的耦合体(除非规范定得很好,能将锁分离,但大部分情况下貌似很难,起码我做不到)

cpucas:比较推荐这种方式,优点很明显,风险低,效率高,缺点也很明显,通常cas只能用于解决单个变量的冲突,一旦涉及到了多个变量,代码就很难写了

同步服务器比较偏向于使用后两种,而普通的web则一般只会用前两种

c.思维方式

这方面其实我一直都没想透

比如通常我们写功能都习惯于使用请求响应模式,但显然这种模式用户体验是偏差的

实际应用中,同步服务器其实能给予更好的用户体验,也就是由服务器去驱动一些事件,注意,这里说的是事件

也就是说服务端给前端返回的不是状态了,而是事件,去驱动前端

虽然写了第四个了,但是由于种种原因,一直都逃离不开请求响应模式,主要不知道前端应该怎么改变它的架构

只是在局部做了一些服务器驱动的功能

2.协议

(指的是flash做客户端,java做服务器端的特殊场景)

由于个人经验也不是太足,这里只讨论两种协议,protobuf跟amf,而也只考虑跟flash进行交互

之前仔细斟酌过amf好还是protobuf好的问题

首先先做一个对比

数据大小:

protobuf无疑是最小的,除了数据,它只多了一个类型编号,跟一个位置索引,当然,该压缩的都压缩了

amf:amf对数据也会做压缩,而对string还会进行重用(这点有可能如果传输大量的类似的文本信息,amf的压缩比会更高),编码方式跟protobuf差别不大

但是有一点很致命,它会将对象的类型信息加进去,包括变量名等信息,也就是说,如果是数组,可能会好一点,如果是每个对象只用一次,传n个对象,无疑这部分的数据量是很大的

效率:

由于游戏服务器基本都是出流量远远高于入流量,这里只考虑服务器向客户端发的情况

protobuf用的方式是生成代码,而amf是反射,虽然没做具体的测试,但无疑,差距应该是很大的,由于是同步服务器一般给前端发的都是短消息,频度高

易用性:

flash默认兼容amf协议,从通道中可以直接简单地解析出对象,非常方便,protobuf则需要编写文件,生成代码,重新编译,数据转换,等等

但是否amf真的完胜了?

经过与我们的首席架构师跟前端的讨论,其实不是这样的

同步服务器一般需要比普通的web更严谨的编码风格,在协议上也是这样的

protobuf定义了前后端交互的协议,非常明确,再也不需要前端debug,查看这样的方式,也就是说,即使我们用amf,我们也需要一份这样的协议,而且是纯手工写的(如一样的dto),个人觉得,会比protobuf产生的更麻烦

当然,为了使用protobuf,还需要一些额外的编码,但这些是一次性的

一比较之下,显然protobuf更好,更优秀,但如果是非常简单的应用,用amf也未尝不可

再补充一点

最近发现前端非常依赖于数据,归根到底其实是amf惹的祸,因为amf返回给前端的是一个动态对象,所以前端想要知道对象里面的值,都习惯性的debug,所以每次,都必须得后端写完,提供数据,前端才能开始做一些事情

这样的前后端合作模式显然是有问题的

相对而言,protobuf会给他们生成一个类文件,对象里面的变量都是确定的,能一定程度上的改变前端这个坏习惯

3.业务架构

现在4个同步项目的业务架构,都沿用了三国里面的面向服务的架构方式,也就是service,bo,这样的方式,在现在所写的4种业务中,这样的方式挺好用的

大体的区别是:

service后面默认都添加了一个参数:“连接上下文”

增加了两个配置文件

把返回的map去掉了,返回数据通过“连接上下文”发送

多了一种叫task的东西,跟bo同一层次,是一种主动运行的事件(这到底在我们业务模型里面应该是一个什么东西呢?之前考虑过让他居于服务层,让他去调用服务,但貌似也不好,因为task调用的逻辑与前端调用的逻辑没有任何地方可重用的,而且会破坏endpoint调用service的方式)

现在有一个问题是:这样的架构如果用于复杂的游戏,例如mmo,能不能用,可用性怎么样?

我们知道,像我们社交游戏的业务,都有一个特点,层次简单,比如过:用户,下一层是兵营,将领,客栈……都处于同一层次上,就没有更深入的层次了

然而复杂的游戏,显然会有很多层,比如说之前写的同步战棋战场,整个服务器有多个战场房间,战场包含着小据点,据点中又包含用户,用户带着将领,都是一层嵌套一层的,这就会我们拿数据的时候,每一个service都需要一层一层地找到数据,然后操作,我们看到的也许就是多个大bo,我们写方法可能都是Abo.doSomething,然后Abo又在方法内调Bbo,这样其实是挺不好的

还有一点,越复杂的业务,task的逻辑就会越多,最后task会越来越多,越来越乱

跟我们的首席架构师多次讨论过这个问题,虽然看法不一致,但我还是觉得,这种业务模型应该还是适用的,起码在有新的方式之前,个人感觉还是利大于弊的

4.异常

在同步服务器中,异常的控制很关键,必须考虑所有可能的情况,定义异常,并捕获,比如之前我在写cas避免冲突的时候,考虑到必须限制cas的次数,否则会造成数据库压力,但是当cas连续失败多次之后,怎么办呢?

以前是直接抛出一个异常,就不管了

但显然,在同步服务器中,这是有严重问题的

我们之所以用同步的方式,除了数据及时性,有一个很重要的部分:通常我们需要写的业务存在着大量的数据竞争。显然用非同步服务器来解决冲突是很困难,并且很不靠谱的一件事,它往往只适合数据分离的业务。

基于这一点,我们如果也像之前一样,抛出一个异常,那很可能会引起数据的不一致,然后是迅速地连锁反应,蔓延,相对而言,我们普通的业务对数据一致性的要求低很多,因为影响的最多是1,2个用户而已

5.同步模型

同步1v1,3v3,老-虎-机用的方式都是房间内单线程,将同步相关的操作封装在了线程池内

国战用的则是锁,所有都是显式加锁,写得很麻烦,而且不好看

总的来说,能不用锁最好还是不用的好,房间内单线程模型,既不用考虑可见性,也不用考虑冲突,而且可以没有限制地使用jdk的集合类,相对起来编码简单很多,房间内操作在锁的层次基本没有任何的耦合,而且不容易错,之前也考虑过,如果写复杂的游戏,也许我们可以把所有场景都抽象成一个个房间,这样,我们只需要考虑房间之间的切换就可以了,然后其他的都是房间内操作,这样的话,我们大部分业务都不需要考虑同步问题了

6.服务器组

我们知道,单服,又不可扩容,对游戏的限制是很大的,于是,有了服务器组的实现(现在做的新游戏原计划是做多区多服的,全部请求都用长连接实现(最后改变了初衷))

所以我们一个区,其实会需求一个服务器组,按业务去划分,比如说:场景1服务器,场景2服务器,交易服务器,战斗服务器,用户状态服务器,等

这其实会有个很关键的需求:网关服务器

既然用了java,肯定希望要用就用全套的,网关服务器也不会考虑用c的实现了

大家都说java比c慢,但慢多少?大家都没有个准确的说法,网上的文章看了几篇,不是测试错误就是检测方法有问题,没有可参照性

按之前在做消息服务器时,与我们的架构师tx做的c版本的比较

c版本:单线程,cpu100%,极限大概是32000qps

java版本:4核多线程,cpu60%-70%,极限大概是68000qps

如果按这个看的话,其实没有太大的区别(个人认为,如果每组能撑10w同时在线,那改为撑7,8w,也不是什么大不了的事情,也就是说性能如果没达到150%,可以忽略不计)

后来也试着在内网做了下压力测试,可是没把服务器压死,先把公司的路由器压死了,后来就没测了

出于这个计划,仔细研究了下mina,netty(还sb兮兮去看了下的游戏开源服务器darkstar,想去看看别人怎么设计网关服务器的,谁知道就一个分布式的业务框架,跟游戏没一毛钱的关系)

发现一个问题,在decoder的时候,mina都会将byte[]从directbuff拷贝一遍到heapbuff中(而且这块代码是写死的,很难动)

如果我们用netty或者mina做这个网关服务器,无疑,我们的做法大部分应该是将byte[]从入口拷贝到出口去,然后发送,这样就会涉及到byte[]的2次拷贝,相当于netty和mina中做的大量的细节优化,都被这简单的两次拷贝给抹杀了

当试着去修改netty的源码时,资料片改为了用类似普通社交的玩法实现,这个计划就不了了之了

之后也去问了下别的公司的开发者(java的),都是说现在这浮躁的网页游戏行业很少有服务器组的实现了,都是单服务器,撑3,5千人就差不多了

c++的代码很多这样的源码,像传奇,魔兽世界,等等,java难道做不了么?

7.时间问题

在同步服务器中,时间是个很恶心的东西,因为客户端是有渲染时间的,但后端没有时间的概念,后端是按事件去触发,去响应的,

举个简单的例子,一个简单的限制,A从x点移动到y点,时间,距离,后端其实是需要检测的,不然就各种外挂,但是如果前端每走一步又跟后端请求一遍,玩家会疯掉的

还有像资料片中的连招的释放,你必须在固定时间内按键,才算正确,又还有国战中我们需要按各自战斗时间决定排队顺序(谁跟谁打,什么时候打)

关于移动网上有的是各式各样的算法,这里就不罗嗦了

说一下在国战中和在资料片中是怎么解决这个问题的

(待续)

相关推荐