背景:
不像node.js是单线程的,我们使用netty都是多线程架构,但是写业务逻辑,其实我们是希望单线程的。
因此我们需要根据模块进行相应的消息分发处理,那就是从channelRead处下手做好分发。
hash映射这种,除了临界情况,其实尽量不应该在业务层出现,除非是到玩家线程的一些(如:房间结算),否则不需要手动映射。
阅读pomelo项目的启发,其实游戏都是有一个模块号的,可以根据模块号做好具体的分发,业务代码写的时候,就可以保证完全按照单线程的逻辑写即可。在zfoo中,其实就是根据玩家请求过来的packet,就可以得到module的name,知道这个请求属于哪个模块。那这个玩家的请求到底投递到哪个线程呢?
1)以斗地主为例子:
1.登录前的模块 "auth":
这个是:在登录账号前,还没有uid时。也就是:拿着account进行登录,此时是直接取
account这个账号作为执行线程。
TaskBus.addTask(account, ()=>{ 这写操作db的逻辑 } ),这样子保证玩家不管怎么登录,都是串行处理,避免一下子同一个账号搞进来2个连接。
总结:先采用默认的sid做路由进入到handler内,执行业务逻辑时,根据accountId做路由。
2.玩家相关的模块 "player":
这个模块处理的都是登录过后只处理个人数据的业务,如:每日登录、引导。
此时都在路由层处理好,
总结:根据uid进行映射到指定的线程。
3.匹配相关的模块 "match":
匹配一般只有一个线程,这样子处理就可以完全无锁化,也无需使用加锁。
但是,匹配成功后要创建房间,此时要先生成房间号,生成房间号可以用内存原子变量,一旦生成房间号后,就可以根据房间号进行操作,往房间管理里面添加room等可以到房间线程执行。
总结:单独独立出来一个匹配线程进行逻辑处理。之后的临界情况:确定下来roomId后就到roomId指定的线程执行。
4.房间相关的模块 "room":
此时都是房间内的逻辑,从玩家Session上要绑定好房间roomId,此时所有的请求都路由到房间线程即可。 也可以将房间线程单独开辟一个房间线程池,这样子就完全没有db操作,会更快一点。 结算之后,再把结果放到玩家线程中进行db相关的处理:
TaskBus.addTask(uid, ()=>{ 这写操作db的逻辑 } ),这样子保证。
总结:房间内逻辑用roomId做路由,结算等涉及多人信息的,采用uid做路由,在里面修改个人数据库。
2)好处:
无需大部分情况下,无需进行手动hash映射(登录前、房间内结算每个人信息相对特殊)
3)要写好单进程多线程类的游戏逻辑
大部分都是分区分服类的游戏,因为方便游戏运营。
一个12c32g内存的游戏服务器,那已经可以支撑很多人了,其实jvm堆内存也要求不可超过32G,JAVA堆大小不要超过32GB!!!_占小狼的技术博客_51CTO博客
----------------------------------------思考--------------------------------
经过上面的分析可以看出来,很多业务中都会存在一些临界情况:
如:登录前不知玩家uid,登录后确定下来玩家uid,但是分配uid和初始化uid对应的玩家数据此时是在登录前的这个线程中。那么后续的存储操作可以提交的uid的线程中。
还有:处理创建房间的handler,此时还没roomId,那么分配roomId和存储这个new出来的Room这个操作也是在默认在创建房间的线程中。那其实后续的操作可以提交到房间线程中。