线程相关
这篇主要介绍框架线程相关的内容。
介绍
框架为开发者提供了线程相关的定制与扩展,开发者可以根据项目需求及特点,来制定符合自身项目的业务线程编排。 框架默认的线程实现比较简单,在接收到 Aeron 消息后,会将消息派发到线程执行器中。 如有特殊业务的,开发者可以在这个阶段进行线程编排。
自定义线程编排的优点与应用场景
默认的策略是通过 userId 获取对应的执行线程,在新开服时,这种默认策略比较好,比较均衡。
但随着时间的推移,服务器总会有用户流失。 此时,我们就可以使用自定义策略,使用其他的业务标识来分配执行线程。 这样依旧能做到线程的均衡执行,从而避免部分线程执行器空闲,部分忙绿的情况。
以上只是其中一个场景的举例,更多的业务场景请自行分析后做定制。 比如,我们可以让某个涉及 IO 的 action 在虚拟线程中执行,以避免阻塞用户线程。
ThreadExecutorRegion
框架内置了 3 个线程执行器管理域,分别是
- UserThreadExecutorRegion,用户线程执行器管理域。
- UserVirtualThreadExecutorRegion,用户虚拟线程执行器管理域。
- SimpleThreadExecutorRegion,简单的线程执行器管理域。
线程执行器管理域中持有多个线程执行器,每个线程执行器中只有一个线程,而线程执行器的主要作用有两点
- 消费任务。
- 规避并发(因为每个线程执行器是单线程的,所以可以很好的避免并发问题)。
线程执行器管理域的线程执行器具体数量是不大于 Runtime.getRuntime().availableProcessors() 的 2n。 当 availableProcessors 的值分别为 4、8、12、16、32 时,对应的数量则是 4、8、8、16、32。
| availableProcessors 值 | Executor[] 数组实际值。(线程执行器) |
|---|---|
| 4 | 4 |
| 8 | 8 |
| 12 | 8 |
| 16 | 16 |
| 32 | 32 |
这样设计的好处是可以通过 userId 快速获取对应的线程执行器
final class UserThreadExecutorRegion extends AbstractThreadExecutorRegion {
...
@Override
public ThreadExecutor getThreadExecutor(long userId) {
return this.threadExecutors[(int) (userId & this.executorLength)];
}
}
如何获取 ExecutorRegion
通过 ExecutorRegion 我们可以获取不同的线程执行器管理域
- UserThreadExecutorRegion
- UserVirtualThreadExecutorRegion
- SimpleThreadExecutorRegion
- By BarSkeleton
- By FlowContext
long userId = ...;
BarSkeleton barSkeleton = ...
ExecutorRegion executorRegion = barSkeleton.executorRegion;
executorRegion.getUserThreadExecutor(userId);
executorRegion.getUserVirtualThreadExecutor(userId);
executorRegion.getSimpleThreadExecutor(userId);
var flowContext = ...
ExecutorRegion executorRegion = flowContext.getExecutorRegion();
UserThreadExecutorRegion
该线程执行器主要用于处理 action 请求。
UserThreadExecutorRegion 是用户线程执行器管理域,而管理域下则会有多个线程执行器。 每个用户会与其中一个线程执行器关联绑定,以避免并发问题。 由于一个线程执行器会被多个用户使用,所以不要在此线程执行器中做耗时的阻塞任务,以免阻塞其他玩家。
Usage Examples
flowContext.executeUser(() -> {
// your code
});
// or
ExecutorRegion executorRegion =...
executorRegion.getUserThreadExecutor(userId);
UserVirtualThreadExecutorRegion
UserVirtualThreadExecutorRegion 是用户虚拟线程执行器管理域,该线程执行器主要用于处理 io 相关的耗时业务,如 DB 入库。
Usage Examples
flowContext.executeVirtual(() -> {
// your code
});
// or
ExecutorRegion executorRegion =...
executorRegion.getUserVirtualThreadExecutor(userId);
SimpleThreadExecutorRegion
SimpleThreadExecutorRegion 是简单的线程执行器管理域,该执行器与 UserThreadExecutorRegion 类似。 如果业务是计算密集型的,又不想占用 UserThreadExecutorRegion 线程资源时,可使用该执行器。
Usage Examples
ExecutorRegion executorRegion =...
var threadExecutor = executorRegion.getSimpleThreadExecutor(userId);
threadExecutor.executeTry(() -> {
// your code
});
如何扩展
自定义 SkeletonThreadPipeline
public final class MySkeletonThreadPipeline implements SkeletonThreadPipeline {
@Override
public void execute(BarSkeleton barSkeleton, FlowContext flowContext) {
// Your code
}
}
添加 SkeletonThreadPipeline 扩展
class OneApplication {
static void main() {
RunOne runOne = new RunOne();
var netServerBuilder = runOne.getNetServerBuilder();
netServerBuilder.setSkeletonThreadPipeline(new MySkeletonThreadPipeline());
runOne
...
.startup();
}
}
小结
框架解决了单个玩家的并发问题,即使玩家重新登录后,也会使用相同的线程来消费业务。 如果开发者有特殊业务的,可以通过重写 SkeletonThreadPipeline 接口来扩展。