信创实战:Nacos 在申威 3231 上的 RocksDB JNI 适配
系列:信创国产化适配 | 作者:lin | 更新:2026-07-18
先把适配对象和结果讲清楚
本文详细记录的源码修改、汇编适配、JNI 编译和 JAR 改造,全部发生在申威 3231 的 SW64 环境。下文的工具链、汇编和构建命令均以 SW64 为目标,不包含龙芯 LoongArch 的编译过程。
rocksdbjni 8.8.1 适配完成后,申威 3231 和 HX8000 均已正常运行。
| 平台 | 原始情况 | 最终结果 |
|---|---|---|
| 龙芯 LoongArch | 当前已有可用支持 | 只作现状对照,不套用申威编译过程 |
| 申威 3231 | 官方 8.8.1 JAR 缺少 SW64 原生库 | 完成 SW64 汇编、编译和 JAR 改造后运行通过 |
| HX8000 | 按现场环境单独验证 | 运行通过 |
原始故障仍要从 Nacos 的实际依赖看起。Nacos 经 JRaft 引入 rocksdbjni 8.8.1,官方通用 JAR 没有携带 SW64 原生库。申威 3231 的适配同时处理了 SWJDK、SW64 ABI、汇编实现、编译器、系统动态库和 Java 资源命名。
截至 2026-07-15,几个常见 Nacos 版本的依赖关系如下:
| Nacos | JDK 要求 | JRaft | rocksdbjni |
|---|---|---|---|
| 2.3.2 | 8 | 1.3.14 | 8.8.1 |
| 2.5.2 | 8 | 1.3.14 | 8.8.1 |
| 3.2.3 | 17 | 1.4.0 | 8.8.1 |
这里的版本来自对应 Nacos 标签下的 pom.xml,以及 JRaft 父 POM 的依赖管理。升级 Nacos 大版本可以获得新的产品能力和安全修复,但截至上述日期,单独升级 Nacos 仍不会自动补齐 SW64 版 RocksDB JNI。
为何官方原始 JAR 在 x86 上可用,在申威上无法启动
rocksdbjni 同时包含 Java 类和按平台编译的本地库。以 Nacos 当前间接依赖的 rocksdbjni 8.8.1 为例,Maven Central 产物包含这些 Linux 文件:
librocksdbjni-linux32.so
librocksdbjni-linux64.so
librocksdbjni-linux-aarch64.so
librocksdbjni-linux-ppc64le.so
librocksdbjni-linux-s390x.so其中没有 SW64 产物。librocksdbjni-linux64.so 是 x86_64 二进制,文件名中的 64 不代表任意 64 位 CPU 都能加载它。
8.8.1 的 Environment 类没有单独识别 sw_64 或 sw64。根据 SWJDK 返回的 os.arch 和加载分支,运行时可能找不到对应资源,也可能落到通用 Linux 文件名并尝试加载 x86_64 二进制。两种情况都会在 JNI 加载阶段失败,常见表现是 UnsatisfiedLinkError。
源码编译、JAR 改造和运行验收要分开看:
- 申威 3231 上修改汇编与编译逻辑,目标是得到 SW64 ELF,不涉及 LoongArch 指令或 LoongArch 工具链。
- 生成
librocksdbjni.so后还要继续验证 Java 类、JNI ABI、动态依赖和 Nacos 数据路径,不能把链接产物直接视为验收结果。
JAR 和 ZIP 的关系
JAR 使用 ZIP 容器格式,但修改 Java 程序包时仍要遵守 JAR 的目录和加载规则。常见 ZIP 容器如下:
| 扩展名 | 容器与用途 | 常见内部内容 |
|---|---|---|
.zip | 普通 ZIP 归档 | 任意文件 |
.jar | Java 程序包,使用 ZIP 容器 | .class、资源、META-INF/MANIFEST.MF |
.xlsx | Office Open XML,使用 ZIP 容器 | XML、样式、表格、关系文件 |
.docx | Word 文档,使用 ZIP 容器 | XML、图片、样式 |
.pptx | PowerPoint 文档,使用 ZIP 容器 | 幻灯片 XML、媒体资源 |
.apk | Android 安装包,使用 ZIP 容器 | classes.dex、资源、AndroidManifest.xml、签名 |
.aar | Android Library,使用 ZIP 容器 | classes.jar、res、AndroidManifest.xml |
.ipa | iOS App 包,使用 ZIP 容器 | Payload/*.app、App 内容 |
Nacos Server JAR 可能在 BOOT-INF/lib 等目录中包含 rocksdbjni-8.8.1.jar,.so 位于内层 JAR 中:
nacos-server.jar 外层 JAR
└── BOOT-INF/lib/rocksdbjni-8.8.1.jar 内层 JAR
├── org/rocksdb/*.class Java 类
├── librocksdbjni-linux64.so x86_64 原生库
└── META-INF/MANIFEST.MF 内层 Manifest下面的命令用于确认内层 JAR 和 .so 的位置:
jar tf nacos-server.jar | grep 'rocksdbjni-.*\.jar'
unzip -p nacos-server.jar BOOT-INF/lib/rocksdbjni-8.8.1.jar \
> /tmp/rocksdbjni-8.8.1.jar
jar tf /tmp/rocksdbjni-8.8.1.jar | grep 'librocksdbjni'实际路径以 jar tf 的输出为准,修改步骤见后文的 Nacos 依赖替换。
先定位,再决定是否编译
不要看到 UnsatisfiedLinkError 就替换 JAR。先把运行架构、依赖版本和本地库格式查清楚。
1. 核对系统与 JVM 架构
uname -m
java -XshowSettings:properties -version 2>&1 | grep 'os.arch'申威环境的输出要以现场系统和 SWJDK 为准,常见值包括 sw_64 或 sw64。系统架构与 JVM 的 os.arch 不一致时,应先核对 JDK 来源和安装包架构。
2. 找到 Nacos 实际携带的 rocksdbjni
如果依赖以独立 JAR 形式存在,可以直接查找:
find /opt/nacos -name 'rocksdbjni-*.jar' -printNacos Server JAR 内嵌依赖时,先查看条目,再把嵌套 JAR 提取出来:
NACOS_JAR=/opt/nacos/target/nacos-server.jar
jar tf "$NACOS_JAR" | grep 'rocksdbjni-.*\.jar'
ROCKSDB_ENTRY=$(jar tf "$NACOS_JAR" | grep 'rocksdbjni-.*\.jar' | head -n 1)
unzip -p "$NACOS_JAR" "$ROCKSDB_ENTRY" > /tmp/rocksdbjni.jar
jar tf /tmp/rocksdbjni.jar | grep -E 'librocksdbjni.*\.(so|jnilib|dll)$'从源码构建 Nacos 时,再用 Maven 确认解析后的版本:
mvn dependency:tree -Dincludes=org.rocksdb:rocksdbjni3. 检查 JAR 内原生库的真实架构
unzip -p /tmp/rocksdbjni.jar librocksdbjni-linux64.so \
> /tmp/librocksdbjni-linux64.so
file /tmp/librocksdbjni-linux64.so
readelf -h /tmp/librocksdbjni-linux64.so | grep 'Machine'在申威 3231 上看到 x86-64,就能确认 JAR 中的原生依赖不能由 SW64 JVM 加载。
本次申威 3231 适配的处理路径
路径一:查找经过验证的 SW64 发行包
操作系统厂商或中间件供应方如果提供 SW64 版 Nacos,需要核对 Nacos、JRaft、rocksdbjni、SWJDK、原生库源码标签、补丁和校验值。本次现场没有直接采用现成的 SW64 发行包,随后走自编译路径完成了申威 3231 适配并运行通过。
路径二:升级整条依赖链
升级 rocksdbjni 时,必须成套更新 Java 类和原生库,并重新验证 JRaft 兼容性。前文列出的 JNI 版本约束同样适用于这条路径。
当前 Nacos 3.2.3 仍解析到 8.8.1。直接升级依赖链属于另一种定制构建,需要覆盖单机重启、配置读写、集群选主、日志追平、快照、备份和恢复。本文跑通的是保留 8.8.1 的 SW64 适配路径。
路径三:保留 8.8.1,在申威 3231 上构建 SW64 原生库
这是本文实际执行的主线。汇编修改、C/C++ 编译、JNI 头文件和 ELF 产物都以申威 3231 为目标。下面保留过程,并在每个阶段标出验证边界。
1. 记录申威构建环境
| 项目 | 本次目标 | 必须留存的证据 |
|---|---|---|
| CPU | 申威 3231,SW64 | uname -m、lscpu |
| OS | 现场申威 Linux 发行版 | /etc/os-release、uname -a |
| JDK | 与 Nacos 版本匹配的 SWJDK | java -version、os.arch、JNI 头文件 |
| 编译器 | SW64 GCC/G++ 和汇编器 | 版本、目标三元组、完整编译命令 |
| Nacos/JRaft | 现场使用版本 | Maven 依赖树 |
| RocksDB JNI | 8.8.1 | Git 标签、提交号和补丁 diff |
先确认系统、JVM、编译器和汇编器确实指向 SW64:
uname -m
lscpu
java -XshowSettings:properties -version 2>&1 | grep 'os.arch'
gcc -dumpmachine
gcc --version
as --version后续出现的汇编和编译输出都来自这套申威工具链。LoongArch 的 -march=loongarch64、LoongArch 汇编文件和对应补丁不应出现在该构建记录中。
2. 安装依赖并准备 SWJDK
RPM 系统可以从下面的依赖集合开始,包名按现场软件源调整:
sudo yum install -y gcc gcc-c++ make cmake git \
snappy-devel zlib-devel bzip2-devel lz4-devel zstd-devel
export JAVA_HOME=/usr/local/swjdk
export PATH="$JAVA_HOME/bin:$PATH"
java -version
test -f "$JAVA_HOME/include/jni.h"
find "$JAVA_HOME/include" -maxdepth 2 -name 'jni_md.h' -printNacos 2.x 使用 JDK 8 时,java、javac、jni.h 和 jni_md.h 应来自同一套 SWJDK。Nacos 3.x 需要 JDK 17,不能继续复用只提供 JDK 8 的申威开发包。
3. 固定与 Nacos 匹配的 RocksDB 源码
cd /opt
git clone --branch v8.8.1 --depth 1 \
https://github.com/facebook/rocksdb.git
cd rocksdb
git rev-parse HEAD本文固定 8.8.1,是为了与 Nacos 经 JRaft 解析出的 Java 层版本保持一致。使用其他版本复现时,要重新核对 JNI 接口和依赖树。
4. 保留 SW64 汇编和编译修改
原始适配包含 SW64 汇编与编译修改,这些修改都服务于申威 3231。早期文章还记录过 env/env_posix.cc 的计时相关调整;该文件属于 C++ 环境实现,不能代替汇编补丁,也不能在缺少编译日志时认定为统一修复方案。
构建分支应保留真实补丁,不要把 LoongArch 的 CMake 提交改写成申威补丁。发布前导出下面几类差异:
git status --short
git diff -- '*.S' '*.s' '*.cc' '*.c' '*.h' \
'Makefile*' 'CMakeLists.txt'
git diff --check
# 保存编译器预定义宏,确认 SW64 条件分支是否命中
printf '' | gcc -dM -E - | sort > sw64-compiler-macros.txt
grep -iE 'sw|alpha|arch' sw64-compiler-macros.txt汇编错误要对应到具体文件、指令和编译命令。若修改了原子操作、CRC、内存屏障、时钟读取或 CPU 探测,应把原始错误、修改前后汇编片段、工具链版本和单元测试结果放进同一份构建记录。只写“加了 SW64 支持”无法复现,也无法判断修改是否破坏并发和持久化语义。
5. 使用申威工具链编译 JNI 目标
make clean
make V=1 -j"$(nproc)" \
LIB_MODE=shared \
DEBUG_LEVEL=0 \
PORTABLE=1 \
EXTRA_CFLAGS="-O2 -fPIC" \
EXTRA_CXXFLAGS="-O2 -fPIC" \
rocksdbjava 2>&1 | tee sw64-rocksdbjava-build.log
find java/target -name 'librocksdbjni*.so' -type f -printV=1 用于保留真实的编译器、汇编器和链接器参数。生成 .so 表示构建走到了产物阶段,后面仍要检查 ELF、动态依赖、JNI 加载和 Nacos 数据路径。
6. 检查 SW64 ELF、依赖和校验值
SO_FILE=$(find java/target -name 'librocksdbjni*.so' -type f | head -n 1)
test -n "$SO_FILE"
file "$SO_FILE"
readelf -h "$SO_FILE"
readelf -Ws "$SO_FILE" | grep 'JNI_OnLoad\|Java_org_rocksdb' | head
ldd "$SO_FILE"
sha256sum "$SO_FILE"file 和 readelf 必须显示申威 SW64 对应的 ELF 机器类型,ldd 不应出现 not found。通过这些检查后,再进入 Java 加载和 Nacos 运行验证。本次申威 3231 完成后续改造并运行通过。
7. 保留未裁剪副本,再做 strip
cp --preserve=mode,timestamps "$SO_FILE" "${SO_FILE}.unstripped"
strip --strip-unneeded "$SO_FILE"
file "$SO_FILE"
sha256sum "$SO_FILE" "${SO_FILE}.unstripped"未裁剪副本用于失败时解析符号和堆栈。产物体积受工具链、链接方式和调试信息影响,不写固定的缩减比例。
用外置 SW64 原生库接入 Nacos
RocksDB 的 NativeLibraryLoader 会先调用 System.loadLibrary("rocksdbjni")。可先把同版本 SW64 候选库外置,减少手工重打两层 JAR 带来的变量。
install -d -m 0755 /opt/nacos/native
install -m 0755 /path/to/sw64/librocksdbjni.so \
/opt/nacos/native/librocksdbjni.so
file /opt/nacos/native/librocksdbjni.so
readelf -h /opt/nacos/native/librocksdbjni.so
ldd /opt/nacos/native/librocksdbjni.so
export JAVA_OPT="-Djava.library.path=/opt/nacos/native ${JAVA_OPT:-}"
sh /opt/nacos/bin/startup.sh -m standalone部署脚本或 systemd 单元也要固定 JAVA_OPT。如果构建系统只生成带平台后缀的文件,保留原文件用于制品归档,另安装一份 librocksdbjni.so 供 System.loadLibrary 查找。
外置加载失败时,先保存完整 UnsatisfiedLinkError、dmesg、ldd 和 /proc/<pid>/maps,再根据日志修正本地库。
修改 rocksdbjni Java 层和 JAR
团队需要把 SW64 文件放回 JAR 时,Java 层生成的资源名必须与 JAR 条目一致。Java 类、SW64 原生库和 Maven 版本也要一起管理。
1. 修改正确的 Environment.java
8.8.1 的源码路径是:
java/src/main/java/org/rocksdb/util/Environment.java在现有类中增加 SW64 判断,并在 Unix 分支加入 SW64 文件名。下面只展示新增判断,其他操作系统分支保留 8.8.1 原实现:
public static boolean isSw64() {
return ARCH.contains("sw_64") || ARCH.contains("sw64");
}
public static String getJniLibraryName(final String name) {
if (isUnix()) {
final String arch = is64Bit() ? "64" : "32";
if (isPowerPC() || isAarch64()) {
return String.format("%sjni-linux-%s%s", name, ARCH, getLibcPostfix());
} else if (isS390x()) {
return String.format("%sjni-linux-%s", name, ARCH);
} else if (isSw64()) {
return String.format("%sjni-linux-sw64%s", name, getLibcPostfix());
} else {
return String.format("%sjni-linux%s%s", name, arch, getLibcPostfix());
}
}
// 其他操作系统分支保留 8.8.1 原实现
}这段修改只负责选择 librocksdbjni-linux-sw64.so,不会修正原生库内部的汇编、ABI 或动态依赖。SWJDK 实际返回值必须通过 os.arch 命令确认。
2. 编译 Java 类和 SW64 原生库
make clean
make V=1 -j"$(nproc)" \
LIB_MODE=shared \
DEBUG_LEVEL=0 \
PORTABLE=1 \
EXTRA_CFLAGS="-O2 -fPIC" \
EXTRA_CXXFLAGS="-O2 -fPIC" \
rocksdbjava 2>&1 | tee sw64-rocksdbjava-build.log
JAR_FILE=$(find java/target -name 'rocksdbjni-8.8.1*.jar' -type f | head -n 1)
SO_FILE=$(find java/target -name 'librocksdbjni*.so' -type f | head -n 1)
test -n "$JAR_FILE"
test -n "$SO_FILE"
# 8.8.1 的 NativeLibraryLoader 从 JAR 根目录读取原生库
cp "$SO_FILE" java/target/librocksdbjni-linux-sw64.so
jar --update --file "$JAR_FILE" \
-C java/target librocksdbjni-linux-sw64.so
jar tf "$JAR_FILE" | grep -E 'Environment.class|librocksdbjni-linux-sw64.so'如果 JAR 没有包含修改后的 Environment.class,应回到 Java 编译步骤检查源码目录和构建目标。只添加 .so,运行时仍可能按旧逻辑寻找 librocksdbjni-linux64.so。
3. 发布独立的私有 Maven 版本
不要覆盖官方 8.8.1 坐标。使用带 SW64 标识的版本,防止相同坐标在不同机器上对应不同内容:
mvn install:install-file \
-Dfile="$JAR_FILE" \
-DgroupId=org.rocksdb \
-DartifactId=rocksdbjni \
-Dversion=8.8.1-sw64.1 \
-Dpackaging=jar在 Nacos 根 POM 的 dependencyManagement 中显式覆盖传递依赖:
<dependency>
<groupId>org.rocksdb</groupId>
<artifactId>rocksdbjni</artifactId>
<version>8.8.1-sw64.1</version>
</dependency>重新构建后确认依赖树,并保存制品校验值:
mvn dependency:tree -Dincludes=org.rocksdb:rocksdbjni
mvn -DskipTests clean package
sha256sum "$JAR_FILE"4. 在 Nacos 中替换依赖
Nacos JAR 不能用“解压、替换 .so、重新压缩”的方式修改。将上面生成的 rocksdbjni 8.8.1-sw64.1 发布到 Maven 仓库,在 Nacos POM 中覆盖依赖,再重新构建 Nacos。本次适配按该方式构建,运行验证通过。
验收不能停在“端口已监听”
端口监听只能证明 Java 进程走到网络初始化阶段。适配验收还要覆盖本地库加载和 RocksDB 持久化路径。
# 检查启动异常
grep -RniE 'UnsatisfiedLinkError|rocksdb|jni' /opt/nacos/logs
# 确认进程映射的是 SW64 库
PID=$(pgrep -f 'nacos-server.jar' | head -n 1)
grep 'librocksdbjni' "/proc/$PID/maps"随后使用与服务端版本匹配的 Nacos 客户端完成配置发布和读取,重启节点后再次读取。本次申威 3231 已经跑通这条适配链路。集群部署还要停止当前 Leader,确认重新选主和日志追平正常。验收记录至少保存 Nacos、JRaft、RocksDB、JDK、操作系统、内核、编译器、补丁提交和制品校验值。