[游戏服务器从入门到关门]RPG游戏服务器常用线程模型参考

699 阅读5分钟

v2-04745d6e2b94364963ffcf1256e63eb0_b.png

这里先说说分线程或使用线程的几个默认原则:

  • a.线程的数据尽量线程自己操作,不要使用了多线程而满不在乎的去操作设置一大堆公共变量让其竞争,这样是不良好的。线程开启的目的前几篇文章已经说了。再提一嘴,虽然有大量的并发集合供大家使用,比如大家津津乐道的ConcurrentHashMap。这些集合只是在无法避免竞争的情况下才使用,比如玩家集合(登录要使用,场景会使用,接口要使用)
  • b.如无必要,不增加线程。要精准使用线程,高阻塞、高计算、不容失败等等因素才开线程,不要滥用,要精准把控项目里线程的数量和具体的用处。否则出了多线程问题,查问题查死你。

1.网络线程(这里使用Netty)

Netty本身在线程方面已经规划好网络方面的使用,所以我们就遵照他的方式来,一个Boss几个Worker。

由于Boss几乎负责监听链接,所以不太可能是并发的瓶颈,默认设置1个就好。有场景交互的游戏,我们的逻辑是丢在场景线程组里执行的,所以Worker只负责处理网络传输和解析相关的事情,这里给4个就好。

如果是H5相关的强单机游戏,建议逻辑就放在Worker里面就行了。

线程安全的问题:Netty会保证一次会话(Channel)始终在一个EventLoop里面执行,所以不用担心这个问题。(具体参考Netty里面的Register事件)

关于Netty的机制,我到后面会讲到。

2.登录线程

为什么登录会单独设置一个线程。

登录其实是一个非常简单的逻辑,从数据库、缓存或第三方拉取数据,校验成功后,获取相应角色,把角色加载到场景中。所以你看登录是有IO操作的,IO往往比较费时,所以但单独弄一个线程。

那为什么是单线程而不用Netty自带的Worker线程做登录操作呢。

其一是,登录如果搞成并发,则会造成很多数据的竞争,特别是在顶号的处理上会比较麻烦。

其二是,经过测试,登录单线程的情况下,并不会成为整个应用的瓶颈,特别是在玩家做了LRU缓存的情况下。

其三是,Worker线程跟网络相关,特别是解码编码之类的,我们一定是要让玩家先链接上来,把登录消息发送后,快速反馈结果,而不是卡在网络层,我们情愿让玩家的登录线程排队而不是在网络线程卡住。

3.交互线程

重点:你可以把客户端当作手柄,交互线程的内容当作玩家的真实数据,场景线程当作玩家手柄操作的木偶。那么最终的过程是,玩家手柄连接服务器,服务器给手柄分配帐号权限,选择角色的时候,服务器会把角色放进场景里,然后让手柄有权操作这个角色。

v2-c8cf06d0fe67fa1bcfc622cf092c54bb_b.jpeg

场景角色就是交互数据的一个投影。

交互作为单线程,也是有原因的。

a.频率低:相对于场景的高频操作来说,交互的频率是非常非常低的,所以并不会出现性能问题。

b.保证安全:既然性能要求不高,那么我们就干脆用单线程的方式保证玩家的数据一致。玩家共同捐献物资到帮会,拍卖行,摊位(非地图交易)等等,都可以放心大胆的写单线程逻辑。

4.场景线程组

场景是游戏中的重中之重,冲着这个地位,我们就可以给他分配最好的资源。但还是那句话,线程合适就行了,不要太多。

线程组中的每个线程管理着N个地图,当然,可以按照场景的重要性,给某些场景单独配备线程也是无可厚非的。

线程线程保证同地图的玩家数据一致性(包括交易)。

场景是高频逻辑,所以更不能出多线程问题。

切换场景一定要先在原场景(线程)注销,再同步切换到另外一个场景中去。

场景线程组和交互线程协作的时候,一定要保证场景线程的顺畅,可以放肆阻塞交互线程。

具体做法给一个参考。假设玩家捐献帮会资源。

第一步、交互线程检测帮会信息。

第二步、跳转到场景线程,阻塞交互线程(Callable和Future的阻塞),等待玩家场景玩家资源扣除。

第三部、增加帮会资源。

5.调度线程

调度线程非常简单,一个Scheduler或者Timer,只要有时间(毫秒级或者Cron表达式控制)到达,则发送一个Runnable或者Callable到场景或交互线程执行一次。时间调度线程可以是多个,但最后都必须发送到相应线程去执行,保证数据一致。

6.接口线程

接口线程负责提供OpenAPI,就是开放的接口。比如场景人数审计,充值回调等。