本机多进程部署
ionet 支持将对外服、注册中心、逻辑服部署在一个进程中,也支持拆分到多个进程中。
本篇以 ionet-multi-process 示例为基础,介绍本机多进程部署的基本做法。
介绍
在开发阶段,我们通常会使用单进程方式启动服务,调试成本更低。 当业务逐渐复杂,或者希望将不同逻辑模块独立扩缩容时,就可以将服务拆分为多个进程。
本机多进程部署的核心思路是:
- 保留一个聚合进程,负责启动
CenterServer、对外服以及Aeron MediaDriver。 - 每个逻辑服作为独立进程启动,并通过 Aeron IPC 接入聚合进程。
- 逻辑服之间仍然可以通过
FlowContext.call()像调用本地逻辑一样互调。
这种本机多进程方式的好处是:
- 逻辑模块可以独立部署、独立重启。
- 开发阶段仍然保留单体式编程体验,业务代码几乎不需要因为部署方式而改动。
- 同机部署时可继续利用 Aeron IPC 的低延迟特性,进程间通信成本很低。
关于 RunOne、CenterServer 与 Aeron 的基础概念,可先阅读 RunOne。
示例结构
示例源码:
- https://github.com/iohao/ionet-examples
- path :
ionet-multi-process
项目结构如下:
ionet-multi-process
├── common-data # 共享数据:命令号、Aeron 配置、工具类
├── logic-book-library # 图书馆逻辑服(独立进程)
├── logic-author # 作者逻辑服(独立进程)
├── one-application # 聚合服务(独立进程)
└── one-client # 测试客户端
各模块职责如下:
| 模块 | 作用 |
|---|---|
one-application | 启动 Aeron MediaDriver、CenterServer 和对外服 |
logic-book-library | 图书馆逻辑服,提供 listBook 业务 |
logic-author | 作者逻辑服,对外暴露 hello 与 listBook |
common-data | 存放模块号、命令号、Aeron 连接配置等共享内容 |
one-client | WebSocket 客户端,用来验证部署结果 |
架构图
one-client (WebSocket)
│
▼
one-application ←── Aeron IPC ──► logic-book-library
(CenterServer (BookLibraryLogicServer)
ExternalServer cmd=1: listBook
MediaDriver)
│
└─── Aeron IPC ──────────► logic-author
(AuthorLogicServer)
cmd=2: hello, listBook
在这个结构中:
- 客户端只连接
one-application暴露的 WebSocket 端口。 one-application内嵌MediaDriver,并作为各逻辑服接入的统一入口。logic-book-library与logic-author通过相同的 Aeron 目录连接到MediaDriver。AuthorAction.listBook会跨进程调用BookLibraryAction.listBook。
聚合进程
one-application 是本机多进程部署中的关键进程,它负责三件事:
- 启动
Aeron MediaDriver。 - 启动唯一的
CenterServer。 - 启动对外服,接收客户端请求。
public final class OneApplication {
static void main() {
var aeron = new EmbeddedAeronRuntime().getAeron();
new RunOne()
.setAeron(aeron)
.enableCenterServer()
.setExternalServer(
ExternalMapper.builder(ExternalGlobalConfig.externalPort).build()
)
.startup();
}
}
从上面的代码可以看出:
enableCenterServer()启动注册中心,只在聚合进程中调用一次。- 对外服也只在这里启动。
- 逻辑服进程本身不直接暴露给客户端。
我们也可以将对外服单独用一个进程来启动,这样可以更灵活地控制对外服的部署。
逻辑服进程
每个逻辑服都可以单独启动,只需要将自身的 LogicServer 注册到 RunOne 中即可。
public final class BookLibraryApplication {
static void main() {
CoreGlobalConfig.setting.parseDoc = true;
var aeron = LogicAeronClientFactory.create();
new RunOne()
.setAeron(aeron)
.setLogicServerList(List.of(new BookLibraryLogicServer()))
.startup();
}
}
logic-author 的启动方式与此相同,只是替换成 AuthorLogicServer。
这意味着每个逻辑模块都可以:
- 独立成进程。
- 独立发布。
- 按业务压力单独扩容。
Aeron IPC 连接
本机多进程部署能成立的关键,在于多个进程使用同一套 Aeron 目录进行 IPC 通信。
示例中,聚合进程与逻辑服进程都使用了相同的目录名:
String aeronDirectoryName =
"%s-%s".formatted(CommonContext.getAeronDirectoryName(), "ionet");
聚合进程在 EmbeddedAeronRuntime 中创建 MediaDriver,并在关闭时清理目录。
逻辑服进程通过 LogicAeronClientFactory.create() 连接到该目录。
因此在启动顺序上必须注意:
- 先启动
one-application。 - 再启动各个逻辑服。
如果逻辑服先启动,它们将找不到可连接的 MediaDriver。
进程间调用
虽然 logic-author 与 logic-book-library 已经拆成两个进程,但业务调用方式仍然很直观:
@ActionMethod(AuthorCmd.listBook)
public List<String> listBook(FlowContext flowContext) {
var response = flowContext.call(BookLibraryCmd.of(BookLibraryCmd.listBook));
return response.listString();
}
被调用方只需要正常声明 Action:
@ActionController(BookLibraryCmd.cmd)
public final class BookLibraryAction {
AtomicInteger inc = new AtomicInteger(0);
@ActionMethod(BookLibraryCmd.listBook)
public List<String> listBook() {
return List.of("book-" + inc.incrementAndGet(), "book-" + inc.incrementAndGet());
}
}
从业务代码视角看:
- 调用方不需要关心目标逻辑服是否在同一进程。
- 部署形态变化不会破坏既有业务调用方式。
这也是 ionet 在本机多进程场景下仍然能保持良好开发体验的原因之一。
命令号划分
示例中的模块号与命令号如下:
| 模块 | cmd | subCmd | 描述 |
|---|---|---|---|
logic-book-library | 1 | 1 | listBook,获取图书列表 |
logic-author | 2 | 1 | hello,问候接口 |
logic-author | 2 | 2 | listBook,跨进程获取图书列表 |
共享命令定义放在 common-data 中,这样多个进程可以复用同一套协议定义。
启动步骤
在示例根目录执行打包:
mvn clean package
会生成以下可执行 jar:
| 模块 | JAR 路径 |
|---|---|
one-application | one-application/target/one-application.jar |
logic-book-library | logic-book-library/target/logic-book-library.jar |
logic-author | logic-author/target/logic-author.jar |
one-client | one-client/target/one-client.jar |
第一步:启动聚合进程
java --add-opens java.base/jdk.internal.misc=ALL-UNNAMED \
--enable-native-access=ALL-UNNAMED \
-jar one-application/target/one-application.jar
该进程会同时启动:
MediaDriverCenterServer- WebSocket 对外服
如果没有自定义端口,对外服默认使用 ExternalGlobalConfig.externalPort,对应的 WebSocket 地址通常为 ws://127.0.0.1:10100/websocket。
第二步:启动逻辑服进程
# 图书馆逻辑服
java --add-opens java.base/jdk.internal.misc=ALL-UNNAMED \
--enable-native-access=ALL-UNNAMED \
-jar logic-book-library/target/logic-book-library.jar
# 作者逻辑服
java --add-opens java.base/jdk.internal.misc=ALL-UNNAMED \
--enable-native-access=ALL-UNNAMED \
-jar logic-author/target/logic-author.jar
这两个进程启动顺序不限,但都必须晚于 one-application。
第三步:启动客户端验证
java -jar one-client/target/one-client.jar
客户端会自动发送两组请求:
AuthorCmd.helloAuthorCmd.listBook
其中 AuthorCmd.listBook 最终会跨进程调用 logic-book-library,如果你在日志中看到了图书列表返回,就说明整个多进程链路已经打通。
使用建议
在实际项目中,可以按下面的思路拆分:
- 将对外服、注册中心、网关职责集中放在一个或少量入口进程。
- 按业务域拆分逻辑服,例如大厅服、房间服、背包服、匹配服。
- 将共享协议、命令号、公共 DTO 放到独立模块中,供多个进程复用。
小结
ionet 的本机多进程部署并不会改变业务编程模型。 开发者只需要:
- 保证只有一个
CenterServer。 - 让聚合进程先启动
MediaDriver。 - 让各逻辑服通过相同的 Aeron 配置接入。
这样就可以在保持开发体验的同时,获得更灵活的部署形态与更清晰的服务拆分边界。