对外服设计
如果你不关心对外服整体脉络与设计的,只想知道如何使用、如何做功能扩展等内容, 请阅读对外服介绍。
无锁异步化、事件驱动的架构设计;真轻量级,无需依赖任何第三方中间件就能搭建出一个分布式的网络通信服务器。
自带负载均衡、分布式支持、可动态增减机器。
| 名称 | 扩展方式 | 职责 |
|---|---|---|
| ExternalServer | 分布式 | 对外服的职责是与用户连接、交互 |
| LogicServer | 分布式 | 逻辑服的职责是处理具体业务逻辑 |
从架构简图中,我们知道了整体架构由对外服和逻辑服组成,两者既可相互独立,又可相互融合。
对外服在架构图位置的左边部分,External。
介绍
在阅读本文时,需要你具备一定的 Netty 知识。 通过本篇的学习,你可以知道对外服的整体设计。 了解整体设计后,你可以
- 扩展更多的连接方式。
- 添加一些自定义的 Netty Handler。
- 集成第三方通信框架,框架对外服默认的实现是 Netty。
UML
我们先看对外服的 UML 图。
对外服总体来说只有两个核心接口,如果你只打算做功能扩展,通常只需要关注 MicroBootstrapFlow 接口。
MicroBootstrapFlow 接口的职责是业务编排、功能扩展,并帮助开发者屏蔽连接方式的细节, 如 TCP、WebSocket、UDP、KCP、QUIC,框架默认提供了基于 Netty 的 TCP、WebSocket、UDP 实现。
MicroBootstrap 接口的职责是与真实玩家连接的服务器,帮助开发者屏蔽通信框架,如 Netty、Mina、smart-socket,框架默认提供了基于 Netty 的实现。
MicroBootstrap
MicroBootstrap 接口的职责是与真实玩家连接的服务器,帮助开发者屏蔽通信框架,如 Netty、Mina、smart-socket,框架默认提供了基于 Netty 的实现。
开发者如有需要,可以考虑基于 Mina、smart-socket 等通信框架来提供一个对外服的实现。 即使采用 Mina、smart-socket 这些通信框架提供的对外服实现,也不会影响现有的逻辑服业务逻辑。 这是因为对外服实现了单一职责原则,只负责用户连接相关的维护。
该设计可以使得对外服的扩展变得更加简单和灵活。
MicroBootstrapFlow
MicroBootstrapFlow 是开发者在扩展时接触最多的一个接口。
职责 : 业务编排、功能扩展,并帮助开发者屏蔽连接方式的细节, 如 TCP、WebSocket、UDP、KCP、QUIC,框架默认提供了基于 Netty 的 TCP、WebSocket、UDP 实现。 开发者可以扩展不同的连接方式,如 TCP、WebSocket、UDP、KCP、QUIC 等。
你可以通过该接口添加一些自定义的 Netty Handler,来定制符合自身项目的业务。
业务编排分为构建时、新建连接时两种。
- createFlow: 构建时,此时服务器还没启动。该方法是构建时的执行流程,方法内调用了 option、channelInitializer 方法。
- option,可以给服务器做一些 option 设置。
- channelInitializer,可以给服务器做一些业务编排。
- pipelineFlow: 新建连接时,服务器已经启动,每次有新连接进来时,会触发。
该方法是新建连接时的执行流程,方法内调用了 pipelineCodec、pipelineIdle、pipelineCustom 方法。
- pipelineCodec,编解码相关的编排。
- pipelineIdle,心跳相关的编排。
- pipelineCustom,自定义的业务编排。
连接方式
目前,框架已经提供了 TCP、WebSocket 和 UDP 连接方式,并且支持在这几种连接方式之间进行切换,并且不会对业务代码造成任何影响。
| 连接方式 | 框架是否提供 |
|---|---|
| TCP | ✅ |
| WebSocket | ✅ |
| UDP | ✅ 企业级功能 |
| KCP | ❌ (在计划中) |
| QUIC | ❌ (在计划中) |
扩展连接方式
MicroBootstrapFlow 在扩展上也是简单的,对外服在扩展无连接的 UDP 时, 只用了 400+ 行 java 代码就完成了与 TCP、WebSocket 相兼容的扩展(包括路由权限、心跳、UserSession 管理...等)。 这意味着,将来我们实现 KCP、QUIC 的扩展时,也是简单的。
| 连接特性 | 类名 | 已提供的连接方式 | 计划中的连接方式 |
|---|---|---|---|
| 长连接 | SocketMicroBootstrap | TCP、WebSocket | QUIC |
| 无连接 | UdpMicroBootstrap | UDP | KCP |
channelInitializer
方法在初始化 Netty ChannelInitializer 时,在方法内调用了 pipelineFlow 方法, 而 pipelineFlow 内默认编排了 pipelineCodec、pipelineIdle、pipelineCustom 方法的执行流程。 开发者可以任选一个方法来重写,但大部分情况下只需要重写 pipelineCustom 就可以达到很强的扩展了。
- pipelineCodec,编解码相关的编排。
- pipelineIdle,心跳相关的编排。
- pipelineCustom,自定义的业务编排,大部分情况下只需要重写 pipelineCustom 就可以达到很强的扩展了。
abstract class AbstractSocketMicroBootstrapFlow implements MicroBootstrapFlow<ServerBootstrap> {
...
@Override
public void channelInitializer(ServerBootstrap bootstrap) {
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
pipelineFlow(new DefaultPipelineContext(ch, setting));
}
});
}
}
public interface MicroBootstrapFlow<Bootstrap> extends ExternalSettingAware {
...
void channelInitializer(Bootstrap bootstrap);
default void pipelineFlow(PipelineContext pipelineContext) {
pipelineCodec(pipelineContext);
pipelineIdle(pipelineContext);
pipelineCustom(pipelineContext);
}
}
pipelineCustom
pipelineCustom 是自定义的业务编排方法,开发者可以通过重写此方法,编排自己的 Handler,以满足你项目的业务需求。 默认情况下,框架为 pipelineCustom 方法添加了一些内置的 Netty Handler。
@FieldDefaults(level = AccessLevel.PROTECTED)
abstract class AbstractSocketMicroBootstrapFlow implements MicroBootstrapFlow<ServerBootstrap> {
...
@Override
public void pipelineCustom(PipelineContext context) {
if (ExternalGlobalConfig.enableLoggerHandler) {
context.addLast("SimpleLoggerHandler", SimpleLoggerHandler.me());
}
// Check for route existence.
context.addLast("CmdCheckHandler", CmdCheckHandler.me());
// UserSession
SocketUserSessionHandler socketUserSessionHandler = setting.option(SettingOption.socketUserSessionHandler);
context.addLast("UserSessionHandler", socketUserSessionHandler);
// Route access authentication
SocketCmdAccessAuthHandler socketCmdAccessAuthHandler = setting.option(SettingOption.socketCmdAccessAuthHandler);
context.addLast("CmdAccessAuthHandler", socketCmdAccessAuthHandler);
// ExternalServer data cache
if (ExternalGlobalConfig.externalCmdCache != null) {
context.addLast("CmdCacheHandler", CmdCacheHandler.me());
}
// Forward the request to the logic server.
var userRequestHandler = setting.option(SettingOption.userRequestHandler);
context.addLast("UserRequestHandler", userRequestHandler);
}
}
如何扩展 Netty Handler
我们编写一个自定义的 Netty Handler,之后添加到处理链中。
public final class MyNettyHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("hello:" + msg);
super.channelRead(ctx, msg);
}
}
重写 pipelineCustom 方法,并添加 MyNettyHandler 到处理链中。
- code 10: 添加自定义的 MyNettyHandler 类。
- code 11: 使用框架的默认编排。
private static ExternalServer ofExternalServer() {
var externalServerBuilder = MyExternalServer.builder(
ExternalGlobalConfig.externalPort
, ExternalJoinEnum.WEBSOCKET
);
externalServerBuilder.setMicroBootstrapFlow(new WebSocketMicroBootstrapFlow(){
@Override
public void pipelineCustom(PipelineContext context) {
context.addLast(new MyNettyHandler());
super.pipelineCustom(context);
}
});
return externalServerBuilder.build();
}
重写方法时需要注意你所使用的连接方式。
- ExternalJoinEnum.WEBSOCKET: 对应 WebSocketMicroBootstrapFlow 类。
- ExternalJoinEnum.TCP: 对应 TcpMicroBootstrapFlow 类。
何时重写相关方法
- 如果框架默认提供的编解码部分不能满足你的业务,可以选择重写 pipelineCodec 方法。
- 如果框架默认提供的心跳部分不能满足你的业务,可以选择重写 pipelineIdle 方法。
- 如果框架默认提供的内置 Handler 部分 不能满足你的业务,或者说想做功能增强的,可以选择重写 pipelineCustom 方法。
- 如果 pipelineCodec、pipelineIdle、pipelineCustom 方法都不能满足你的业务,可以直接重写 pipelineFlow 方法。
- 其他的方法亦是如此。
小结
我们介绍了 MicroBootstrap、MicroBootstrapFlow 这两个核心接口及其职责。
- MicroBootstrap 职责是与真实玩家连接的服务器,帮助开发者屏蔽通信框架,如 Netty、Mina、smart-socket,框架默认提供了基于 Netty 的实现。
- MicroBootstrapFlow 职责是业务编排、功能扩展,并帮助开发者屏蔽连接方式的细节。
在 MicroBootstrapFlow 中做功能扩展和业务编排是简单的,只需要重写 pipelineCustom 方法。