← 返回

信创实战: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 版本的依赖关系如下:

NacosJDK 要求JRaftrocksdbjni
2.3.281.3.148.8.1
2.5.281.3.148.8.1
3.2.3171.4.08.8.1

这里的版本来自对应 Nacos 标签下的 pom.xml,以及 JRaft 父 POM 的依赖管理。升级 Nacos 大版本可以获得新的产品能力和安全修复,但截至上述日期,单独升级 Nacos 仍不会自动补齐 SW64 版 RocksDB JNI。

flowchart TB A["Nacos / JRaft"] --> B["rocksdbjni 8.8.1"] B --> C["SW64 汇编、JNI 编译与 JAR 改造"] C --> D["申威 3231、HX8000 运行通过"]

为何官方原始 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_64sw64。根据 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 归档任意文件
.jarJava 程序包,使用 ZIP 容器.class、资源、META-INF/MANIFEST.MF
.xlsxOffice Open XML,使用 ZIP 容器XML、样式、表格、关系文件
.docxWord 文档,使用 ZIP 容器XML、图片、样式
.pptxPowerPoint 文档,使用 ZIP 容器幻灯片 XML、媒体资源
.apkAndroid 安装包,使用 ZIP 容器classes.dex、资源、AndroidManifest.xml、签名
.aarAndroid Library,使用 ZIP 容器classes.jarresAndroidManifest.xml
.ipaiOS 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_64sw64。系统架构与 JVM 的 os.arch 不一致时,应先核对 JDK 来源和安装包架构。

2. 找到 Nacos 实际携带的 rocksdbjni

如果依赖以独立 JAR 形式存在,可以直接查找:

find /opt/nacos -name 'rocksdbjni-*.jar' -print

Nacos 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:rocksdbjni

3. 检查 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,SW64uname -mlscpu
OS现场申威 Linux 发行版/etc/os-releaseuname -a
JDK与 Nacos 版本匹配的 SWJDKjava -versionos.arch、JNI 头文件
编译器SW64 GCC/G++ 和汇编器版本、目标三元组、完整编译命令
Nacos/JRaft现场使用版本Maven 依赖树
RocksDB JNI8.8.1Git 标签、提交号和补丁 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' -print

Nacos 2.x 使用 JDK 8 时,javajavacjni.hjni_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 -print

V=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"

filereadelf 必须显示申威 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.soSystem.loadLibrary 查找。

外置加载失败时,先保存完整 UnsatisfiedLinkErrordmesgldd/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、操作系统、内核、编译器、补丁提交和制品校验值。

参考资料