跳到主要内容

本机多进程部署

提示

ionet 支持将对外服、注册中心、逻辑服部署在一个进程中,也支持拆分到多个进程中。 本篇以 ionet-multi-process 示例为基础,介绍本机多进程部署的基本做法。

介绍

在开发阶段,我们通常会使用单进程方式启动服务,调试成本更低。 当业务逐渐复杂,或者希望将不同逻辑模块独立扩缩容时,就可以将服务拆分为多个进程。

本机多进程部署的核心思路是:

  1. 保留一个聚合进程,负责启动 CenterServer、对外服以及 Aeron MediaDriver
  2. 每个逻辑服作为独立进程启动,并通过 Aeron IPC 接入聚合进程。
  3. 逻辑服之间仍然可以通过 FlowContext.call() 像调用本地逻辑一样互调。

这种本机多进程方式的好处是:

  1. 逻辑模块可以独立部署、独立重启。
  2. 开发阶段仍然保留单体式编程体验,业务代码几乎不需要因为部署方式而改动。
  3. 同机部署时可继续利用 Aeron IPC 的低延迟特性,进程间通信成本很低。

关于 RunOneCenterServerAeron 的基础概念,可先阅读 RunOne

示例结构

示例源码:

项目结构如下:

ionet-multi-process
├── common-data # 共享数据:命令号、Aeron 配置、工具类
├── logic-book-library # 图书馆逻辑服(独立进程)
├── logic-author # 作者逻辑服(独立进程)
├── one-application # 聚合服务(独立进程)
└── one-client # 测试客户端

各模块职责如下:

模块作用
one-application启动 Aeron MediaDriverCenterServer 和对外服
logic-book-library图书馆逻辑服,提供 listBook 业务
logic-author作者逻辑服,对外暴露 hellolistBook
common-data存放模块号、命令号、Aeron 连接配置等共享内容
one-clientWebSocket 客户端,用来验证部署结果

架构图

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

在这个结构中:

  1. 客户端只连接 one-application 暴露的 WebSocket 端口。
  2. one-application 内嵌 MediaDriver,并作为各逻辑服接入的统一入口。
  3. logic-book-librarylogic-author 通过相同的 Aeron 目录连接到 MediaDriver
  4. AuthorAction.listBook 会跨进程调用 BookLibraryAction.listBook

聚合进程

one-application 是本机多进程部署中的关键进程,它负责三件事:

  1. 启动 Aeron MediaDriver
  2. 启动唯一的 CenterServer
  3. 启动对外服,接收客户端请求。
public final class OneApplication {

static void main() {
var aeron = new EmbeddedAeronRuntime().getAeron();

new RunOne()
.setAeron(aeron)
.enableCenterServer()
.setExternalServer(
ExternalMapper.builder(ExternalGlobalConfig.externalPort).build()
)
.startup();
}
}

从上面的代码可以看出:

  1. enableCenterServer() 启动注册中心,只在聚合进程中调用一次。
  2. 对外服也只在这里启动。
  3. 逻辑服进程本身不直接暴露给客户端。
提示

我们也可以将对外服单独用一个进程来启动,这样可以更灵活地控制对外服的部署。

逻辑服进程

每个逻辑服都可以单独启动,只需要将自身的 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

这意味着每个逻辑模块都可以:

  1. 独立成进程。
  2. 独立发布。
  3. 按业务压力单独扩容。

Aeron IPC 连接

本机多进程部署能成立的关键,在于多个进程使用同一套 Aeron 目录进行 IPC 通信。

示例中,聚合进程与逻辑服进程都使用了相同的目录名:

String aeronDirectoryName =
"%s-%s".formatted(CommonContext.getAeronDirectoryName(), "ionet");

聚合进程在 EmbeddedAeronRuntime 中创建 MediaDriver,并在关闭时清理目录。 逻辑服进程通过 LogicAeronClientFactory.create() 连接到该目录。

因此在启动顺序上必须注意:

  1. 先启动 one-application
  2. 再启动各个逻辑服。

如果逻辑服先启动,它们将找不到可连接的 MediaDriver

进程间调用

虽然 logic-authorlogic-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());
}
}

从业务代码视角看:

  1. 调用方不需要关心目标逻辑服是否在同一进程。
  2. 部署形态变化不会破坏既有业务调用方式。

这也是 ionet 在本机多进程场景下仍然能保持良好开发体验的原因之一。

命令号划分

示例中的模块号与命令号如下:

模块cmdsubCmd描述
logic-book-library11listBook,获取图书列表
logic-author21hello,问候接口
logic-author22listBook,跨进程获取图书列表

共享命令定义放在 common-data 中,这样多个进程可以复用同一套协议定义。

启动步骤

在示例根目录执行打包:

mvn clean package

会生成以下可执行 jar:

模块JAR 路径
one-applicationone-application/target/one-application.jar
logic-book-librarylogic-book-library/target/logic-book-library.jar
logic-authorlogic-author/target/logic-author.jar
one-clientone-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

该进程会同时启动:

  1. MediaDriver
  2. CenterServer
  3. 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

客户端会自动发送两组请求:

  1. AuthorCmd.hello
  2. AuthorCmd.listBook

其中 AuthorCmd.listBook 最终会跨进程调用 logic-book-library,如果你在日志中看到了图书列表返回,就说明整个多进程链路已经打通。

使用建议

在实际项目中,可以按下面的思路拆分:

  1. 将对外服、注册中心、网关职责集中放在一个或少量入口进程。
  2. 按业务域拆分逻辑服,例如大厅服、房间服、背包服、匹配服。
  3. 将共享协议、命令号、公共 DTO 放到独立模块中,供多个进程复用。

小结

ionet 的本机多进程部署并不会改变业务编程模型。 开发者只需要:

  1. 保证只有一个 CenterServer
  2. 让聚合进程先启动 MediaDriver
  3. 让各逻辑服通过相同的 Aeron 配置接入。

这样就可以在保持开发体验的同时,获得更灵活的部署形态与更清晰的服务拆分边界。