- 消息队列
- 后端
- 通信
【免费下载链接】aeron
Efficient reliable UDP unicast, UDP multicast, and IPC message transport
本指南以 aeron-system-tests/scripts/provisioning/README.md 为核心,结合仓库中
fabfile.py、RemoteEchoTest与ProvisioningServerMain等源码,系统讲解 Aeron 如何通过 Fabric v2 在远程服务器上自动部署aeron-alljar、拉起回显(Echo)服务并以 JMX 驱动跨主机 UDP 绑定测试。读完本文,你将掌握fab五段式命令的完整用法、每个任务的底层实现、JMX 控制通道的工作方式,以及排查部署问题的关键点。
一、这套脚本要解决什么问题
Aeron 的消息传输支持 UDP 单播、UDP 组播与 IPC 三种方式。当需要验证 Aeron 在不同操作系统、不同网络栈(Bindings)上的真实收发行为时,最可靠的手段是跑系统级测试——尤其是让两个主机之间通过真实网络完成数据回环的RemoteEchoTest。
aeron-system-tests/scripts/provisioning目录下的脚本正是为此而生:它借助 Python 生态的 Fabric 部署工具 v2(官方文档见 https://docs.fabfile.org),把"构建、上传、远程启动服务、执行测试、清理"整条链路自动化,使开发者只需一条命令即可在远程主机上完成一次完整的绑定测试。
整个流程在 README.md 中被归纳为 5 个步骤:
- 构建一份
aeron-all-<version>.jar - 将 jar 拷贝到远程服务器
- SSH 登录远程服务器,启动一个远程资源调配服务(provisioning service)实例
- 运行
RemoteEchoTest - 停止该服务
二、环境要求与运行前提
脚本基于Fabric v2(其底层依赖 Invoke),需要先安装:
pip install fabric同时还需满足以下前提:
- 必须从项目根目录运行(README 明确说明 "It should be run from the root directory of the project"),因为脚本内部使用
./gradlew、version.txt、aeron-all/build/libs/...等相对项目根目录的路径; - 本机与远程主机之间已配置好 SSH 免密登录(Fabric 依赖 SSH 连接执行远程命令);
- 远程主机已安装Java,并在调用
deploy时通过--java-home指明 JDK 目录; - 远程主机的10000 端口可供本机访问——该端口是部署脚本指定的 JMX/RMI 远程管理端口(详见下文)。
三、命令总览
README 给出的完整用法如下:
$ fab -r aeron-system-tests/scripts/provisioning \ -H <ip address to run the remote service on> stop \ prepare-deploy \ deploy \ --java-home=<location of the java home directory on the remote server> \ test \ --test-host=<optional, ip address of the host running RemoteEchoTest> \ --aeron-dir=<optional, path to an already running media driver>参数说明:
| 参数 | 含义 | 是否必填 |
|---|---|---|
-r aeron-system-tests/scripts/provisioning | 指定 Fabric 任务(task)所在目录,即 fabfile.py | 必填 |
-H <ip> | 远程服务器的 IP 地址,测试服务将运行在此主机上 | 必填 |
stop | 先清理远程主机上可能残留的旧服务进程(pkill) | 建议 |
prepare-deploy | 本地构建 jar(clean+:aeron-all:jar) | 必填 |
deploy --java-home=<路径> | 上传 jar 并远程启动 provisioning 服务 | 必填 |
test | 本地执行RemoteEchoTest | 必填 |
--test-host=<ip> | (可选)运行测试的主机 IP;默认自动推断 | 可选 |
--aeron-dir=<路径> | (可选)指向一个已运行的 media driver 目录;不填则测试内嵌启动 driver | 可选 |
可以看到命令将stop、prepare-deploy、deploy、test四个任务串联,等价于"旧进程清场 → 构建 → 部署 → 测试"的完整流水线。每个任务的实现细节如下。
四、任务逐层拆解(源码级)
fabfile.py基于 Invoke 的@task()装饰器定义了 4 个任务,外加一个辅助的version任务。
4.1 prepare-deploy:本地构建 aeron-all jar
@task() def prepare_deploy(c): local("./gradlew --console=verbose clean", env = {"JAVA_HOME": "/home/mike/opt/jdk/jdk8"}) local("./gradlew --console=verbose :aeron-all:jar", env = {"JAVA_HOME": "/home/mike/opt/jdk/jdk8"})该任务在本机执行 Gradle Wrapper,先clean再构建:aeron-all:jar子模块,产物位于aeron-all/build/libs/aeron-all-<version>.jar。需要注意两点:
- 脚本中的
JAVA_HOME是作者本机的/home/mike/opt/jdk/jdk8,这是硬编码的开发环境路径,实际使用时应按本机 JDK 位置修改,或改为从环境变量读取; - jar 的版本号来自仓库根目录的 version.txt(当前为
1.54.0-SNAPSHOT),deploy任务正是读取该文件确定上传文件名。
4.2 version:探测远程 Java 主版本
deploy内部会先调用{java_home}/bin/java -version,再通过parse_version正则从输出首行解析主版本号:
def parse_version(version_string): g = re.search("version \"(.*)\\.(.*)\\..*\"", version_string) ... if (g.group(1) == "1"): version = int(g.group(2)) # 如 1.8.0_xxx → 8 else: version = int(g.group(1)) # 如 17.0.x → 17 return version该逻辑兼容 Java 8 时代的1.8.x命名法与 Java 9+ 的17.x命名法,解析出的版本号决定是否需要在启动命令中追加 JVM 参数(见下节)。
4.3 deploy:上传 jar 并远程启动服务
@task() def deploy(c, java_home=None, provisioning_host=None): ... command = [ "{}/bin/java".format(java_home), "-Dcom.sun.management.jmxremote", "-Dcom.sun.management.jmxremote.authenticate=false", "-Dcom.sun.management.jmxremote.ssl=false", "-Dcom.sun.management.jmxremote.port=10000", "-Djava.rmi.server.hostname={}".format(provisioning_host), "-cp ./provisioning/aeron-all-1.38.0-SNAPSHOT.jar", "io.aeron.samples.echo.ProvisioningServerMain", "< /dev/null > ./provisioning/log 2>&1", "&" ] if (8 < java_version): command.insert(1, "--add-opens java.base/jdk.internal.misc=ALL-UNNAMED") ... c.run("rm -rf provisioning") c.run("mkdir -p provisioning") c.put("aeron-all/build/libs/aeron-all-{}.jar".format(lines[0]), "provisioning/.") c.run(" ".join(command)) c.run("sleep 2 ; pgrep -f io.aeron.samples.echo.ProvisioningServerMain")deploy的完整动作链为:
- 解析远程 Java 版本,若主版本 > 8(Java 9 及以上),在命令首部插入
--add-opens java.base/jdk.internal.misc=ALL-UNNAMED——这是 Aeron 使用的 Agrona 底层库访问 JDK 内部 API 所需; - 在远程主机的家目录下
rm -rf provisioning并重建该目录; - 将本机
aeron-all/build/libs/aeron-all-<version>.jar(版本取自 version.txt)上传到远程provisioning/.; - 用
nohup风格的写法(< /dev/null > ./provisioning/log 2>&1 &)在后台启动主类io.aeron.samples.echo.ProvisioningServerMain,日志重定向到./provisioning/log; sleep 2后通过pgrep -f校验进程确实存活,若进程未拉起则该任务报错退出。
JMX 远程管理参数是整套远程控制机制的关键,含义如下:
| JVM 参数 | 作用 |
|---|---|
-Dcom.sun.management.jmxremote | 启用 JMX 远程管理 |
-Dcom.sun.management.jmxremote.authenticate=false | 关闭认证(测试场景) |
-Dcom.sun.management.jmxremote.ssl=false | 关闭 SSL(测试场景) |
-Dcom.sun.management.jmxremote.port=10000 | 暴露 RMI 注册端口 10000 |
-Djava.rmi.server.hostname=<ip> | 指定 RMI 服务对外通告的主机名/IP,防止多网卡下地址推断错误 |
⚠️版本一致性提示:从当前仓库源码看,deploy中上传的 jar 文件名取自version.txt,但启动命令里-cp的 jar 名硬编码为aeron-all-1.38.0-SNAPSHOT.jar(fabfile.py 第 50 行)。若version.txt版本与硬编码值不一致,-cp会指向不存在的文件导致服务启动失败,实际使用时需保持两者同步。
4.4 test:在本地运行 RemoteEchoTest
@task() def test(c, provisioning_host=None, test_host=None, aeron_dir=None): ... command = [ "./gradlew", "-Daeron.test.system.binding.remote.host={}".format(provisioning_host), "--console=verbose", ":aeron-system-test:test", "--tests", "'*RemoteEchoTest'", ] if (not test_host is None): command.insert(1, "-Daeron.test.system.binding.local.host={}".format(test_host)) if (not aeron_dir is None): command.insert(1, "-Daeron.test.system.aeron.dir={}".format(aeron_dir)) local(" ".join(command))测试任务在本机通过 Gradle 执行:aeron-system-test:test,只运行匹配*RemoteEchoTest的用例,并通过三个系统属性向测试传递运行参数:
| 系统属性 | 默认行为 | 作用 |
|---|---|---|
aeron.test.system.binding.remote.host | 必填(-H指定) | 远程 provisioning 服务所在主机 |
aeron.test.system.binding.local.host | 不传则由测试根据远程 IP 自动选择本机网卡 | 本机参与通信的 IP |
aeron.test.system.aeron.dir | 不传则测试内嵌启动 MediaDriver | 复用已运行的 media driver 目录 |
需要说明的是,Gradle 侧测试模块的完整任务名是:aeron-system-tests:test(见 settings.gradle 中aeron-system-tests模块声明),fabfile 中的:aeron-system-test:test与实际模块名存在拼写差异,实际运行时建议核对本仓库的 Gradle 模块名。
4.5 stop:清理远程服务
@task() def stop(c): c.run("pkill -f io.aeron.samples.echo.ProvisioningServerMain", warn=True)用pkill -f精确匹配进程命令行中包含主类名io.aeron.samples.echo.ProvisioningServerMain的进程,warn=True表示即使没有匹配到进程(如首次运行)也不视为失败。该任务通常在deploy之前执行,确保不会与上一次遗留的服务进程冲突。
五、远程服务端:ProvisioningServerMain 剖析
远程主机上启动的ProvisioningServerMain位于 aeron-samples/src/main/java/io/aeron/samples/echo/ProvisioningServerMain.java,它同时扮演三个角色:
- 内嵌 MediaDriver:若未设置
aeron.dir系统属性,则调用MediaDriver.launchEmbedded()在远程主机内嵌启动驱动,并把 driver 目录传给 Aeron 客户端; - Aeron 客户端:
Aeron.connect(context)连接本地 driver; - JMX 服务端:通过
StandardMBean(provisioning, ProvisioningMBean.class)将 Provisioning 注册到平台 MBean Server,ObjectName 为io.aeron:type=Provisioning,name=testing(常量见 ProvisioningConstants.java)。
服务主体是一个 Agrona Agent(roleName为"EchoProvisioningServer"),其doWork()每次轮询做两件事:
pollProvisioningQueue():消费ManyToOneConcurrentArrayQueue<ProvisioningMessage>中的控制消息(CreateEchoPair/RemoveAllEchoPairs);pollEchoPairs():对每个已创建的EchoPair调用poll(),完成"收到请求消息 → 原样回写"的回环转发。
createEchoPair(correlationId, subChannel, subStreamId, pubChannel, pubStreamId)是 JMX 暴露的核心操作:它会为请求通道创建Subscription、为响应通道创建ConcurrentPublication,并将二者封装为EchoPair,同时为每个 pair 注册EchoMonitorMBean(ObjectName 形如io.aeron:type=EchoPair,name=<correlationId>),用于上报getFragmentCount()等监控指标。
六、客户端:RemoteEchoTest 如何与远程服务协作
测试类位于 aeron-system-tests/src/test/java/io/aeron/RemoteEchoTest.java,标注了@BindingsTest(该注解定义于 aeron-test-support/src/main/java/io/aeron/test/BindingsTest.java),说明它属于专门验证网络绑定行为的系统测试。
beforeAll阶段的连接策略体现了整个机制的优雅之处:
- 若设置了
aeron.test.system.binding.remote.host(即远程部署模式),测试通过如下 JMX URL 连接远程服务:service:jmx:rmi:///jndi/rmi://<remoteHost>:10000/jmxrmi并通过
JMX.newMBeanProxy获得ProvisioningMBean与EchoMonitorMBean的本地代理; - 若未设置远程主机(纯本地开发场景),则直接在当前 JVM 内
ProvisioningServerMain.launch(...)并连接平台 MBean Server,测试仍可完整运行——这为不依赖远程环境的日常验证提供了便利。
测试包含两个用例:
- shouldHandleSingleUnicastEchoPair(10 秒超时):构建一对 UDP 单播通道,请求端点为
remoteHost:24324、响应端点为localHost:24325,stream id 分别为 1001/1002,通过 JMX 调用createEchoPair(1, ...)创建回环对,随后本机向远程发布随机数据并订阅回传数据,最终校验EchoMonitorMBean.getFragmentCount() > 0且收发内容完全一致; - shouldHandleTenUnicastEchoPairs(20 秒超时):循环创建 10 对 echo pair(请求端口 24300–24309、响应端口 24400–24409),并发跑完同样的随机数据回环校验,用于验证多通道并发下的可靠性。
测试使用 1 MiB 随机源数据(SOURCE_DATA_LENGTH = 1024 * 1024),通过Publication.offer按随机长度分片发送、Subscription.poll收取并比对,且所有通道都显式设置了rejoin(false)、linger(0)、termLength(1 << 16),保证通道生命周期可控、资源可回收。
七、与 CMake 测试体系的关联
除 Fabric 脚本外,这套系统测试也可由 CMake 驱动运行。在 aeron-system-tests/CMakeLists.txt 中,当开启AERON_SYSTEM_TESTS时,会注册名为java_system_tests_c_media_driver的测试目标,它通过 Gradle Wrapper 执行:aeron-system-tests:cleanTest与:aeron-system-tests:test,并通过-Daeron.test.system.aeronmd.path传入 C 语言版 Media Driver 的可执行文件路径,验证 Java 客户端与 C driver 的组合。这也解释了为何test任务中的--aeron-dir参数同样支持指向"已运行的 media driver"。
八、实操注意事项
- JAVA_HOME 硬编码:
prepare_deploy中JAVA_HOME写死为作者本机路径,实际使用请改为本机 JDK 8 及以上版本路径; - 远程 JDK 必须 >= 8:
deploy会解析远程 Java 版本;Java 9+ 会自动追加--add-opens,Java 8 则无需; - jar 版本一致性:
version.txt中的版本号决定上传文件名,而-cp中的 jar 名在 fabfile 中是硬编码的,两者不一致会导致远程启动失败; - 防火墙/安全组:确保本机能访问远程 10000 端口(JMX/RMI 注册),否则
RemoteEchoTest的JMXConnectorFactory.connect会连接超时; - SSH 免密:Fabric 通过 SSH 执行远程命令,需提前配置密钥认证;
- 复用外部 driver:若远程已运行独立的 MediaDriver,可先设置
aeron.dir再启动ProvisioningServerMain(其launch逻辑只在未设置该属性时才内嵌启动 driver); - 端口占用:测试用例使用 24300–24325 区间的固定端口,若与其他进程冲突需调整通道配置。
九、相关文件索引
- README(本文依据)
- Fabric 任务实现
- RemoteEchoTest 测试类
- 远程服务主类
- Provisioning 核心逻辑
- JMX 常量定义
- 版本文件
- 系统测试的 CMake 集成
这套脚本的价值在于把"跨主机绑定验证"从手工操作(手动构建、scp、ssh 启动、跑测试、kill 进程)压缩为一条fab命令,同时通过 JMX 控制通道把远程服务的生命周期与本地测试的执行过程解耦——无论是 CI 流水线中多主机绑定矩阵验证,还是本地临时搭建跨机回环环境,都能直接复用。
- 消息队列
- 后端
- 通信
【免费下载链接】aeron
Efficient reliable UDP unicast, UDP multicast, and IPC message transport
相关推荐
xberg 平台支持矩阵深度解析:15 种绑定在 7 大平台上的构建覆盖与已知缺口
xberg 平台支持矩阵深度解析:15 种绑定在 7 大平台上的构建覆盖与已知缺口 导读 xberg 是一个以 Rust 为核心的 polyglot 文档智能项
后端AI 应用NLP终极指南:利用Fay框架构建智能虚拟导游系统
终极指南:利用Fay框架构建智能虚拟导游系统 在当今数字旅游时代, 智能虚拟导游 、 实时景点查询 和 多模态交互 已成为提升游客体验的关键技术。Fay框架作为
Carbanak银行木马模拟计划:金融威胁防御测试完整清单
Carbanak银行木马模拟计划:金融威胁防御测试完整清单 Carbanak银行木马模拟计划是一个基于真实攻击技术的开源威胁模拟方案,专为金融机构设计,用于测试
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考