申威 SW64 适配 Nacos 2.x/3.x:RocksDB 编译、瘦身与完整打包指南


[实战] 申威 SW64 适配 Nacos 2.x/3.x:RocksDB 8.8.1 编译、瘦身与完整打包指南

前言

在信创国产化替代的大背景下,将中间件迁移至 申威 SW64(sw_64) 架构是常见需求。Nacos 2.x 版本(1.4前没有)引入了 JRaft 协议,底层强依赖 RocksDB 进行数据存储。

然而,官方发布的 Nacos 包中内置的 RocksDB 动态库缺少申威的 librocksdbjni.so,在申威服务器上启动时会报 UnsatisfiedLinkError。此外,由于 SW64 架构特性,直接编译源码会因"无硬件计时器"报 No timer implementation 错误。

本文记录了在 SW64 + glibc 2.28 环境下,从源码编译 RocksDB 8.8.1,解决计时器报错 ,并通过 Release 模式与 Strip 优化 将动态库体积从 200MB+ 缩减至 20MB 左右,最后演示如何通过手动替换Maven集成 的方式,将其无缝替换进 Nacos,截止25.12.01,nacos2.5.x-3.x,都使用的RocksDB 8.8.1(2023年发布版本)

1. 环境信息

项目 说明
CPU sunway/(3231/WX-8000) sw_64 架构
OS 麒麟 / 统信 UOS 基于 glibc 2.28
JDK SWJDK 8u312 必须包含 javacjni.h
RocksDB 8.8.1 Nacos 2.x/3.x 依赖版本(截止25.12.01)
GCC 8.3.0+ 支持 sw_64 指令集即可
Maven 3.6.0+ 建议较新版本,用于依赖管理

注意问题:SW64 用户态没有可读的 Cycle Counter 寄存器,导致 RocksDB 默认计时逻辑在编译时就会报错,需手动补全,当然很久接触内核cpu之类的,可能是我没找到。

2. 准备编译环境

RocksDB 的 JNI 编译需要 JDK 开发包(Headers)以及一系列压缩库的开发包。

bash 复制代码
# 1. 安装 SWJDK 开发包 (确保有 javac)
sudo yum install -y java-1.8.0-swjdk-devel.sw_64

# 2. 设置环境变量 (编译脚本强依赖 JAVA_HOME)
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-swjdk-8u312.sw_64
export PATH=$JAVA_HOME/bin:$PATH

# 3. 安装 C++ 编译工具链及压缩库依赖
# zlib, snappy, lz4, zstd 是 RocksDB 性能关键
sudo yum install -y gcc gcc-c++ make cmake \
  zlib-devel snappy-devel lz4-devel zstd-devel

3. 源码下载与 SW64 计时器补丁

RocksDB 内部使用 toku_time 获取高精度时间,源码中未适配 SW64。如果不修改,执行 make 时会直接报错 #error No timer implementation

3.1 下载源码

bash 复制代码
wget https://github.com/facebook/rocksdb/archive/refs/tags/v8.8.1.tar.gz
tar xf v8.8.1.tar.gz
cd rocksdb-8.8.1

3.2 修复 No timer implementation (关键)

编辑文件:utilities/transactions/lock/range/range_tree/lib/portability/toku_time.h

在文件末尾的 #elif defined(...) 链条中,加入 SW64 的 clock_gettime 实现。

修改方法 :找到 #else #error No timer implementation... 之前,插入以下代码:

cpp 复制代码
// ----------------- 新增开始 -----------------
#elif defined(__sw_64__) || defined(__SW64__) || defined(__sw_64)
  // SW64 补丁:使用 clock_gettime 替代硬件计数器
  struct timespec ts;
  clock_gettime(CLOCK_MONOTONIC, &ts);
  // 返回纳秒级时间,符合 tokutime_t 特性
  return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
// ----------------- 新增结束 -----------------

#else
#error No timer implementation for this platform
#endif

4. 编译优化与瘦身(关键步骤)

很多同学编译出来的 librocksdbjni.so 动辄 300MB,这在生产环境是不可接受的。
原因分析

  1. 未 Strip:包含了大量供 GDB 使用的调试信息。
  2. 静态链接:RocksDB 为了可移植性(Portable),将 Snappy/Zstd 等库静态打入了 so 中。
  3. Debug 模式 :默认 CMake 配置可能未开启 -O3 优化。

4.1 执行优化编译

我们显式指定 Release 模式,并在编译后执行 strip

bash 复制代码
# 1. 创建构建目录
mkdir build-sw64-opt && cd build-sw64-opt
# 2. CMake 配置
cmake .. \
  -DCMAKE_BUILD_TYPE=Release \
  -DFAIL_ON_WARNINGS=OFF \
  -DWITH_JEMALLOC=OFF \
  -DWITH_SNAPPY=ON \
  -DWITH_LZ4=ON \
  -DWITH_ZLIB=ON \
  -DWITH_ZSTD=ON \
  -DPORTABLE=ON

# 3. 并行编译核心库与 JNI 接口
# PORTABLE=1 确保生成的指令集通用性
# nproc代表多少个线程
PORTABLE=1 make rocksdbjava -j$(nproc)

4.2 手动瘦身 (Strip)

编译完成后,在 java/target/ 下会生成库文件(通常是 librocksdbjni-8.8.1-linux.so)。

bash 复制代码
cd ../java/target/

# 查看原始大小
ls -lh librocksdbjni*.so

# 【建议】方案 A:仅移除 Debug 信息(安全,保留符号表供 Java 调用)
strip --strip-debug librocksdbjni-8.8.1-linux*.so

# 【激进】方案 B:移除所有符号(体积最小,可能丢失部分堆栈信息)
# sudo strip --strip-unneeded librocksdbjni-8.8.1-linux*.so

# 再次查看大小,通常在 10MB - 20MB 之间,完美!
ls -lh librocksdbjni*.so

5. 两种适配路径:源码修改 vs 暴力替换

你可以选择 "正确" (修改 Java 源码) 或者 "邪道" (重命名库文件)

路径一:源码级适配(推荐,彻底解决)

我们修改 RocksDB 的 Java 源码,让它完美支持申威架构。

1. 修改 Environment.java

文件:java/src/main/java/org/rocksdb/util/Environment.java
Step 1: 添加 isSW64() 方法

java 复制代码
  private static boolean isSW64() {
    // 申威架构在 Java os.arch 中通常返回 "sw_64",为了保险把 "sw64" 也加上
    return ARCH.equals("sw_64") || ARCH.equals("sw64");
  }

Step 2: 修改 getJniLibraryName 方法

java 复制代码
  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());
      }
      // ... (其他分支)
      // ----------------- 新增 SW64 分支 -----------------
      else if (isSW64()) {
        // 强制返回 "rocksdbjni-linux-sw_64"
        // 这样 NativeLibraryLoader 就会去拼成 librocksdbjni-linux-sw_64.so
        return String.format("%sjni-linux-sw_64", name);
      }
      // ----------------- 新增结束 -----------------
      else {
        return String.format("%sjni-linux%s%s", name, arch, getLibcPostfix());
      }
    }
    // ...

2. 修改 Makefile (可选)

为了让 make 自动生成的 so 文件名带上 sw_64 后缀:

makefile 复制代码
# 找到判断 MACHINE 的部分,添加 sw_64
ifneq (,$(filter ppc% s390x arm64 aarch64 sparc64 loongarch64 sw_64, $(MACHINE)))
	ROCKSDBJNILIB = librocksdbjni-linux-$(MACHINE)$(JNI_LIBC_POSTFIX).so
else
	ROCKSDBJNILIB = librocksdbjni-linux$(ARCH)$(JNI_LIBC_POSTFIX).so
endif

3. 重新编译 JAR 包

bash 复制代码
# 回到 rocksdb 根目录
cd ../..
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-swjdk-8u312.sw_64

# 清理并重新编译
make jclean
make rocksdbjava -j$(nproc)

此时 java/target/ 下的 jar 包已经是包含 librocksdbjni-linux-sw_64.so 的完全体了。

路径二:暴力替换(重命名欺骗)

如果不想改代码,可以将编译出的 sw_64 so 文件重命名为 librocksdbjni-linux64.so
原理 :官方 JNI 加载器在匹配不到架构时,通常会回退尝试加载 linux64 的库。

  1. librocksdbjni-8.8.1-linux.so 重命名为 librocksdbjni-linux64.so

  2. 更新到 jar 包中:

    bash 复制代码
    jar uf rocksdbjni-8.8.1.jar librocksdbjni-linux64.so

6. Nacos 集成方案

方案 A:Maven 本地安装(最规范,适合重编译 Nacos)

如果你有 Nacos 源码,想重新打个干净的包:

bash 复制代码
# 1. 安装到本地 Maven 仓库
# 假设你在 rocksdb-8.8.1/java/target 目录下
mvn install:install-file \
  -Dfile=rocksdbjni-8.8.1.jar \
  -DgroupId=org.rocksdb \
  -DartifactId=rocksdbjni \
  -Dversion=8.8.1 \
  -Dpackaging=jar

# 2. 编译 Nacos
cd nacos-source
mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U

方案 B:Jar 包修改(最实战,适合已有 Nacos 包)

Nacos Server 是一个 Spring Boot Fat Jar,内部嵌套了多层 Jar 包。直接解压替换容易导致签名错误或包结构损坏,请严格按以下步骤操作。

假设

  • Nacos 安装包:nacos-server.jar
  • 新编译的库:rocksdbjni-8.8.1.jar (上面编译出来的)

步骤 1:解压外层 Jar

bash 复制代码
mkdir work_dir && cd work_dir
cp /path/to/nacos-server.jar .

# 解压
jar -xf nacos-server.jar
# 此时目录包含:BOOT-INF, META-INF, org ...

步骤 2:定位并替换 RocksDB 依赖

RocksDB 的 Jar 包位于 BOOT-INF/lib/ 下。

bash 复制代码
cd BOOT-INF/lib/

# 备份原版
mv rocksdbjni-8.8.1.jar rocksdbjni-8.8.1.jar.bak

# 【关键】将我们编译好的 jar 拷贝进来
cp /path/to/your/compiled/rocksdbjni-8.8.1.jar .

补充:如果你只有 .so 文件,想手动塞进这个 jar:

bash 复制代码
mkdir temp_rocks && cd temp_rocks
jar -xf ../rocksdbjni-8.8.1.jar
cp /path/to/your/librocksdbjni-linux-sw_64.so .
jar -cfM ../rocksdbjni-8.8.1.jar .
cd .. && rm -rf temp_rocks

步骤 3:重新打包外层 Nacos Jar (必须注意压缩参数)

回到 work_dir 根目录。

bash 复制代码
cd ../.. 

# 重新打包为 nacos-server-sw64.jar
# -0 (数字0): 仅存储不压缩。Spring Boot 外层 Jar 推荐不压缩,提高启动速度,防止 Loader 报错
# -c 创建, -f 文件, -M 不生成新清单
jar -cfM0 nacos-server-sw64.jar .

7. 验证与总结

将生成的 nacos-server-sw64.jar 部署到申威服务器。

7.1 启动验证

bash 复制代码
sh bin/startup.sh -m standalone

7.2 日志检查

查看 logs/nacos.log,重点关注是否有以下报错:

  • UnsatisfiedLinkError: 说明 so 文件没加载到,或者名字不对。
  • SIGILL (非法指令): 说明编译时没加 PORTABLE=ON,或者用了错误的 GCC。
    正常的话

7.3 无 VNC 环境验证 (Curl 测试)

在没有图形界面的情况下,使用 curl 验证 RocksDB 功能。

测试配置写入 (Write Test):

bash 复制代码
curl -X POST 'http://127.0.0.1:8848/nacos/v1/cs/configs' \
-d 'dataId=sw64-test' \
-d 'group=DEFAULT_GROUP' \
-d 'content=RocksDB_Works_On_SW64'
# 预期返回: ok之类

测试配置读取 (Read Test):

bash 复制代码
curl -X GET 'http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=sw64-test&group=DEFAULT_GROUP'
# 预期返回: RocksDB_Works_On_SW64

避坑指南总结

  1. 必须补全代码 :没有 toku_time 的 sw_64 分支,RocksDB 无法编译。
  2. 一定要 Strip:不 strip 的库文件巨大,不仅占磁盘,Nacos 启动加载会非常慢。
  3. 打包参数 :重打 Spring Boot Jar 时,务必使用 jar -cfM0,不要用 Zip 工具,否则大概率报 Unable to find main class
  4. Java 版本 :编译 JNI 时,JAVA_HOME 必须指向 SWJDK 的 devel 包路径,否则找不到 jni.h