游戏线程池的设计
在前面的章节中,我们一再提到,游戏的业务逻辑复杂,
很多数据都是放在内存中进行处理的,但是,仅仅放在内存中还不能完全满足要求。
我举个例子,房间 A 里面的四个玩家打牌,如果使用普通的 Java 线程池处理这个房间的所有信息,就会带来新的问题:
1 所有消息对于房间信息的修改,需要使用锁,对性能有一定的影响;
2 使用锁之后,业务实现者要时时考虑锁的使用问题,增加了业务开发的难度;
3 有序性,我们知道 Java 线程池中任务的流转过程,先看有没有达到核心线程数,再看队列有没有满,最后看有没有达到最大线程数,我举个极端的例子,如果队列满了,此时,发来一条消息,它会去尝试创建一个新的线程(未达最大线程数)来处理当前消息,这导致该房间之前的消息可能还在队列中未处理,而这条消息却先处理了,这明显不符合要求。
基于以上三点原因,使用 Java 线程池肯定是不能满足我们的要求了,那么,使用 Netty 的线程池是否可以呢?
所以,我们需要重新设计一种线程池:不加锁且消息有序,这要怎么实现呢?
我们前面提到过一个名词:房间制,那么,是不是把同一个房间的消息发到同一个线程进行处理就可以了呢?
答案确实是这样的,但是实现起来并没有那么简单。
首先,使用 Java 自带的线程池肯定是不行的了。
其次,使用 Netty 的线程池,通过前面关于 Netty 线程池的源码剖析,我们知道,在 Netty 中,有两种选择线程的方式,即 PowerOfTwoEventExecutorChooser 和 GenericEventExecutorChooser,然而,它们的内部实现并没有本质上的区别,都是通过轮询的方式调用线程池中的线程来处理任务,所以,乍看之下,也不满足我们的要求。
最后,只能自己来实现了?自己实现的话,也是每个线程维护自己的队列,且把同一个房间的消息都到同一个线程的队列中,这其实跟 Netty 还是有些像的,而且,自己实现的线程池,在稳定性、安全性等方面的考量,也是一个非常重要的问题,所以,我们能不能对 Netty 的线程池进行改造,拿过来即用呢?
通过前面的分析,可以发现,其实 Netty 的线程池本身是满足我们的要求的,关键在于选择线程的方式这里,所以,改造的要点就在这里,我们需要自己实现一种线程选择的方式,比如,按房间号去选择线程。
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
}
在调用 next () 方法选择线程的时候,似乎不能传递额外的参数了,但是,别忘了,我们还有终极杀器 —— 线程本地变量,在调用 next () 方法之前,可以把房间号存储在 FastThreadLocal 中,这样在 next () 中就能获取到这个房间号,然后,就可以使用房间号来做路由了,比如按房间号取模,完美 ^^
游戏线程池的实现
参考 DefaultEventExecutorChooserFactory 的代码,我们实现一个自己用的 MahjongEventExecutorChooserFactory,并将其运用到自定义的线程池 MahjongEventExecutorGroup 中:
public class MahjongEventExecutorGroup extends MultithreadEventExecutorGroup {
private MahjongEventExecutorGroup(int nThreads) {
// 使用自定义的选择器工厂
super(nThreads, null, new MahjongEventExecutorChooserFactory(), null);
}
@Override
protected EventExecutor newChild(Executor executor, Object... args) throws Exception {
return new DefaultEventExecutor(this, executor);
}
// 工厂类
private static class MahjongEventExecutorChooserFactory implements EventExecutorChooserFactory {
@Override
public EventExecutorChooser newChooser(EventExecutor[] executors) {
return new MahjongEventExecutorChooser(executors);
}
// 真正使用的选择器
private static class MahjongEventExecutorChooser implements EventExecutorChooserFactory.EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
MahjongEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
// 从上下文中取出当前的房间id
Long roomId = MahjongContext.currentContext().getCurrentRoomId();
long id;
if (roomId != null) {
id = roomId;
} else {
// 没获取到房间号的消息轮徇扔到业务线程池中处理
// 他们往往跟房间信息没啥关系,比如登录请求
id = idx.getAndIncrement();
}
// 根据id取模选择线程执行
return executors[(int) (id & executors.length - 1)];
}
}
}
}