游戏服务器中多线程之间如何通信

 二维码 139
发表时间:2018-10-20 00:12

在游戏服务器中,线程的管理是重中之重,在上一篇文章中,已经简单说明了,在游戏服务器开多少线程合适的问题,点击这里查看  ,因为线程数量不能太多,所以为了提高游戏服务器并发性,就需要在线程处理业务的速度要快,不能长时间卡住线程,比如,不能有网络io,磁盘IO等耗时的操作。所以我们会把有限的线程数进配按需分配。线程的主要分配方式如下:

1,与客户端的IO线程

负责接收客户端消息,和向客户端发送消息。

2,处理玩家业务的逻辑线程

负责业务逻辑的处理与计算。

3,处理rpc或数据库同步的网络线程。

负责不同服务之间的通信

4,处理日志的磁盘IO 线程

相应的线程分别做自己该做的时间。但是这时候问题来了,既然按用途分配了这些线程,那不可免费的就会出现不同线程之间的数据交互了。举个例子来说,比如玩家登陆和排队,假如说一个区一台物理机,处理与客户端的IO线程占1个,业务逻辑线程16个,请求数据库的线程3个。其它的先忽略不算。这个时候,服务器收到客户端登陆的请求,业务线程收到这个请求,第一件事情是去数据库查用户的信息,因为业务逻辑线程是多个用户之间共用的,所以你不能在这里等待数据库请求的返回,要不然会卡别的业务逻辑处理。最好的做法就是,把这个请求封装成一个事件,发送到数据库同步线程中去处理这个事。等待数据库线程处理完了,再把结果告诉业务逻辑线程,然后再处理业务,这个时候处理的数据都在内存中了,所以速度非常快,处理完之后再返回客户端。

有人说,那这样还不是卡登陆吗?是的,必卡登陆,因为处理数据库的线程就那么3个。对于mysql的查询性能测试如下:

我们3个线程按1500个计算,那么也只有登陆并发达到1500的时候,才会觉得服务慢一些,(实际情况可能会再底一些),如果并发能达到这么高,说明游戏很火了,可以等待数钱了。如果再多的人,我们就可以使用排队的功能的。可以查询到数据库线程中正在等待执行任务的数量,如果达到某个值,就可以在业务服务中给客户端返回正在排队的,让客户端过一会再来请求即可。

回到正题,那么游戏服务器线程之间该如何传递数据呢,可能每个人的做法不一样,这里只列举中一个,希望给大家给带来些参考,就当是交流学习了。如果你有更好的方法,也希望您评论分享。

在Java中,多线程之间交互数据,即A线程给B线程一个请求事件,A线程还要获取B线程的执行结果,比如登陆,请数据库线程去查库,查完之后告诉逻辑线程。Java提供了一个Future/Callable的机制,详细大家可以自动百度它们的用法,但是它们有一个缺点,就是A线程在获取结果时(调用future.get())的时候是同步的,如果B线程没有执行完,还是会卡A线程。这显示不是我们想要的。对于这一点,我们伟大的异步框架,netty的作者给出了一个解决方法,那就是Future / promise模式。这里对它的源码暂不做分皙了,有时间另写文章说明吧,有兴趣的同学可以自己查看。这里只说一下它的用法。

此代码只是模拟,真实应用中还需要自己设计,首先是数据库查询的管理类:

package com.xinyue.demo.future;

import io.netty.util.concurrent.DefaultEventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;

public class MysqlManager {

DefaultEventExecutor threadB = new DefaultEventExecutor();

// 获取当前任务数量
public int getTaskCount() {
return threadB.pendingTasks();
}

public Future<User> queryUser(String name, String pasword, Promise<User> promise) {
threadB.execute(() -> {
// 这里在B线程中执行果查询数据的操作
User user = null;// 这里由查询结果赋值
promise.setSuccess(user);
});

return promise;
}
}

然后是业务管理类:

package com.xinyue.demo.future;

import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.DefaultEventExecutor;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.Promise;

public class LogicManager {

EventExecutor threadA = new DefaultEventExecutor();
private MysqlManager mysqlManager;

public LogicManager(MysqlManager mysqlManager) {
this.mysqlManager = mysqlManager;
}
/**
* 这里模拟的就是业务线程收到登陆的请求之后,然后业务线程要查数据库,但是查询数据库的操作是在另外一个线程中执行的,查询结果出来之后,再返回结果。
* @param name
* @param password
* @param ctx
*/
public void login(String name,String password,ChannelHandlerContext ctx) {
//这里创建Promise的时候,传入线程A,表示这个promise中的任何操作都会在线程A中发生。
Promise<User> promise = new DefaultPromise<>(threadA);
//这里面是一个异步调用,所以不会卡当前的业务线程A.
Future<User> future = mysqlManager.queryUser(name, password, promise);
future.addListener(new GenericFutureListener<Future<User>>() {

@Override
public void operationComplete(Future<User> future) throws Exception {
//这里会在promise调用setSuccess的时候时候执行,而且这个监听方法的执行是在A线程中。
User user =future.get();
//这里就拿到了用户的信息了,然后可以做一系列的业务操作,完成之后,再返回给客户端消息。
ctx.writeAndFlush("登陆成功");
}
});
}
}


通过这个例子,其它关于多线程交互数据的功能实现,大家就可以举一返回三了。


欢迎加群交流,QQ群:66728073,197321069,398808948 还可以扫描博客左上角二维码,关注游戏技术网公众号