抽个时间,把java语言开发游戏服务器的技术盲点补上,方便复习。
注:这里仅仅是讨论流程解决方案,具体的细节等,不作说明。
1.网络通讯
1.1 游戏客户端与游戏服务器的通讯
客户端复杂多变,比如传统页游flash,手机游戏,h5等,所用消息协议不同,但无非是tcp,http,websocket等。
jdk传统的阻塞io模式,显然无法满足游戏高并发的需求。即使是nio(非阻塞io),对于研发人员来说开发成本大,错误率高,对于以后游戏扩展
也是一个极大考验。
1.7以后,jdk提供了aio(windows上是通过iocp实现了真正的异步io)。这样所有的读、写、连接等通知,都通过
事件来驱动。由于linux上未实现真正的异步,而且相比较nio,aio的效率提升不明显,目前这种方式使用不多。后面会单独一篇文章来说明如何
使用aio。
java语言的伟大之处,不仅仅是在语言本身,各种各样的优秀框架也极大的简化开发复杂度。对于游戏通讯这块,netty(目前4版本较多)必然是
首选。netty实在太优秀了,这里不作太多介绍,如果netty不了解的,可以看我前几篇博客的源码,或者买本关于netty的书籍。
1.2 客户端与服务器约定速成的通信规则
通讯框架我们已经选用netty了,我们已经接受到了客户端的连接,网络传输的码流如何解析成程序熟悉的pojo对象,如何避免粘包,半包,
接着如何处理客户端发送的消息,服务器又是如何通知客户端呢?
- 与客户端约定完整的一套,比如长度size(int),msgId(short),content(xxx),自定义解析协议
- protobuf
- 特殊分隔符等等
通过上面几种方案,可以简单的解决粘包和半包问题,netty通过某种神奇的手段(哒哒。。。)终于拿到了客户端发送的消息内容。
这里netty处理的内容很多,比如channelHandler的链式流程,比如ssl认证,读写超时,tcp最大连接数设置等,我就不细说了。
2.复杂的线程模型
游戏的线程模型,较为复杂,画图比较容易理解。这里我就偷懒,概述一下常见的解决方案。
2.1我们知道,在1网络通讯里所说的一切消息编解码,转换pojo对象等,都是io做的事。io的线程数量,也是伴随着游戏类型和复杂度
不同而不同,我们通常的设置是:cpu+1。
2.2上文中,我们通过某种神奇的手段,拿到了客户端发送的消息内容,这里依然是io线程,你可以想象下,如果客户端发送一个A消息,
让我们从数据库load 10W条数据,这里会有一定的延迟操作,然而对于密集型的任务如果存在延迟,那么玩家登陆、发送消息等会存在卡顿,
这对于rpg的游戏来说,简直就是灾难。
2.3既然有卡顿了,那简单啦,加个业务线程池,这个线程池的数量通常是:cpu。业务线程池执行我们业务代码逻辑,客户端发送A消息,
我们把A消息封装成task,放在线程池里最终执行。
好像可以了?
我们现在假设,客户端先发了A消息,再发B消息,按照tcp和netty的处理机制,转到游戏这边应该是A-->B的过程。但是我们抛到线程池了,
如何保证线程池执行的有序性?阻塞队列吗?他们都不在一个thread里。
2.4如何处理玩家消息的有序性?
客户端与服务器的链接,相当于一个对话,有对话就一定有channel,有channel有一定有session(我封装的xxSession来管理channel)。
针对同一个玩家用户,session不变,session管理的channel不变,那么channelId就不变。
每次客户端不管是发送A消息,还是发送B消息,他们的channelId都不变。所以在抛到线程池执行前,做个简单的hash算法,保证
相同的channel都在同一个线程里处理。
2.5还有吗?
比如工会、氏族、抢购等业务,设计到公共数据的地方,玩家必然会有交互,如何保证数据最终的一致性呢?
有的朋友说加锁,对,锁是处理这种交互数据最简单,也是最暴力的一种方式。这里我引入了actor模型,我通过对比发现actor的模型
不是一般的好,他不仅可以共用线程池,同时还能让所有的任务分片执行,不至于让某个线程一直忙于处理某个集中型的任务。
如果对于actor不熟悉的同学,可以借鉴akka actor,我这里用的是java actor。具体不细说。
2.6没错,上面的流程只是适用于slg,棋牌类型的游戏,如果是针对rpg的场景类型呢?
解决方案就是加上玩家在场景的场景拷贝,之前2.4以玩家的channelId 进行hash索引,这里以场景的sceneId为单位即可。
3.跨服、多服的简单解决方案
上面2步实现了单机最简单的消息投递方式,新的问题又来了,如果把网关服,用户服独立开,场景服务器独立开,跨服等如何实现呢?
java多进程之间通讯,看来rpc要派上用场了。游戏这里不比电商等业务,我们所有的服务都是同java语言开发维护,所以一个简单的chen-rpc就可以搞定啦。
rpc具体的实现细节,不作过多讨论。我们实现的最终目标,远程调用,就像本地调用一样的方便即可。
4.游戏后台管理系统
简单的前端框架xx抄过来,连ui都不用啦。
http服务,自己写了一个,tomcat都省了,gm指令,web后台登录权限控制,运营客服线上操作等,恩,不用太复杂。
当然,运营让你复杂,还是要搞一搞的。
5.游戏静态数据的配置维护
策划经常更改静态表数据,对服务器来说,这种常驻内存的数据,
如果更改一次,服务器重启一次,这样不仅仅是内网测试困难,线上怎么办呢?
5.1首先,策划表数据配置一般都在excel中,这里需要提供一个Excel解析工具,将excel数据,转换成data.dat配置,或者json等,最终生成在服务器项目下的resource
资源包下。
5.2策划更新维护时,通过4的游戏后台,上传data.dat文件,同时重新reload静态资源数据,将java内存值更改 。
6.动态数据的更新维护fixme
有静态数据,那必然有要动态更改的数据,这种数据如何落地?会有缓存穿透吗?如果线上有段代码需要更新,怎么办?
6.1目前我接触的几款游戏项目,选用的数据库都是mysql,同时游戏服务器内部有缓存。具体的缓存同步到数据库方式,一般有两种:
- 阻塞的队列,队列中有数据,就往数据库更新
- 定时消费,同步数据库
好了,orm开始登场啦。游戏的数据类型简单,表设定更容易,需要事务吗?应该是不需要的。
所以,除了那些大的框架,比如hibernae,jpa,ibatis等,我们可以自己写一个简单的orm,具体参考:apache下的dbUtils
6.2java的一系列因素,决定了动态更新一定不是件容易的事。目前动态更新有多种,具体不作详细介绍。但是,原理无外乎两种:
- 重写classLoader
- 关于Agent Main
7.寻路、视野
3d和2d目前我接触的两款游戏设定不一样,具体的我单独分篇来介绍,这里不过多说明。
8.游戏压测
这个我比较擅长了,目前也是主要干这个事情,具体参考我上篇关于机器人压测的介绍。
9.游戏日志系统
日志系统很重要,不管是运营正常的数据需求,还是我们程序线上查找问题,日志都是比较完美的解决方案。
具体也不细说了。
10.战斗系统+棋牌算法
10.1 slg回合制战斗
10.2 rpg即时性战斗
10.3 斗地主等
单独的文章来说明,具体不介绍了,每个玩法也不尽相同。