问题背景
在将包含 DuckDB 依赖的程序部署至 CentOS 7 上时,运行 DuckDB 时出现了如下报错:
java.lang.UnsatisfiedLinkError: /tmp/libduckdb_java6604742508229353124.so:
/lib64/libm.so.6: version `GLIBC_2.23' not found (required by /tmp/libduckdb_java6604742508229353124.so)
为何会出现这个报错?DuckDB 是嵌入式内存数据库,在 DuckDB Jdbc 中其实也包含了 DuckDB 本身,我们可以在源码中找到一大堆 native 方法,这其实就是 DuckDB 在访问其 c++ 语言编译的程序。
这就导致在引入其 jdbc 依赖时会有对除 Java 以外环境的其他需求,如在 Linux 系统上运行时需要较高版本的 Glibc。然而我们公司的程序,特别是 Docker 环境是基于 CentOS 的,现存的 CentOS 包括版本 7 / 8 的 Glibc 版本都较低,完全无法满足 DuckDB 的需求,如何在 CentOS 上运行 DuckDB 就成了程序适配的最大阻碍。
事实上,由于 Java 本身的跨平台特性,在调研阶段根本不会想到这个问题 —— DuckDB 这个做法实际上违背了 Java 精神。而在开发阶段,由于本地运行 MacOS 完全满足 DuckDB 的需求,所以这个问题直到测试阶段在 Docker 上运行时才被发现。
解决方案
相对轻松的两个解决方案——
- 在 Docker File 基于 CentOS 构建容器时便一起更新 Glibc 版本。
❌ 由于 Glibc 是 Linux 系统运行的基本库,包括相当数量的命令,组件库都依赖这个运行,一旦更新,很可能出现系统崩溃、不稳定等问题,这对于已经在使用 CC 的用户来说是个“定时炸弹”,我们完全无法预料可能在哪个环节出现问题。
- 更换更高的 CentOS 版本,或兼容 CentOS 的版本
❌ 很遗憾所有稳定且免费的 CentOS 版本的 Glibc 版本都较低。最新的 CentOS Stream 9 似乎可以解决问题但他是面向开发者的非稳定版本,不建议在生产使用。 我尝试在兼容 CentOS 的 Rocky Linux 构建,但是实际上还是有些许不同,我们的 DockerFile 需要大批量改动,影响很大。
最后似乎只剩下一条路可以走了 —— 构建兼容旧 Linux 系统的 DuckDB Jdbc
我尝试向 Mother Duck 寻求帮助,然而由于时差问题,一次完整的问答至少需要 1 天时间,而且他们也不一定会协助我们解决问题。现在比较急急国王,等不了这么久,只能拿出中国老传统——自己动手,丰衣足食。
为何我们自己构建 DuckDB 能解决这个问题呢?
C++ 和 Java 不同,Java 构建出来的运行包只依赖于特定的 JDK,只要平台上能正常安装上对应版本的 JDK 就一定跑 Java,但是 C++ 构建出来的运行包会依赖原生系统,而 glibc 是 Linux 系统中最底层的 API,若是构建时的版本太高,低版本的 glibc 就无法兼容。换言之,如果通过低版本的 glibc 来构建运行包,就能解决这个问题。
构建 DuckDB Jdbc
由于需要让 duckdb 适配尽可能多的机器,我们需要准备两套不同架构(AMD/ARM)下的 Linux 系统。当然,建议先尝试使用 AMD 64 的服务器构建,ARM 的环境相对难搭。
环境准备[AMD64]
-
CentOS7 AND64 服务器
-
手动下载 DuckDB Jdbc 1.2.2 源码 并上传(阿里 ECS 不能用魔法,只能本地施展并上传)
# 附上 scp 命令示例 scp ./duckdb-java-1.2.2.0.tar.gz root@xx.xx.xx.xx:~/
- 安装 CMake cmake3
- 安装 C++
- 安装 Java
(安装意外的没碰到太大的问题,说实话我都准备一下午就跟他耗上了,以前每次装 C 环境都是大折磨)
很好还是碰到了(QAQ)
其他流程都相对轻松,重点关注 C++ 的安装
CMake
yum install -y epel-release
yum install -y cmake3
which cmake3
sudo ln -s /usr/bin/cmake3 /usr/bin/cmake
C++ 11
通过 yum install -y gcc-c++ 安装的 C++ 只有版本 4.8.5,版本太低,而官方要求 11 及以上,只能通过其他方式来,可以通过 scl 工具来切换。
yum install -y centos-release-scl
yum install -y scl-utils
这个是 RedHet 推出的一个能帮助切换高版本 C++ 的方便工具,但是由于停止维护了,在下载好后你会发现 yum 功能无法正常使用了,需要去修改 repo
在安装后会在 /etc/yum.repos.d 下发现新增了两个文件 CentOS-SCLo-scl.repo 和 CentOS-SCLo-scl-rh.repo(部分版本没有这个,但不影响)。
在 CentOS-SCLo-scl.repo 中修改 [centos-sclo-sclo] 中的内容
[centos-sclo-sclo]
name=CentOS-7 - SCLo sclo
baseurl=https://mirrors.aliyun.com/centos/7/sclo/x86_64/sclo/
# mirrorlist=http://mirrorlist.centos.org?arch=$basearch&release=7&repo=sclo-sclo
gpgcheck=0
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-SCLo
在 CentOS-SCLo-scl-rh.repo 中修改(没有该文件则直接新加)[centos-sclo-rh] 中的内容
[centos-sclo-rh]
name=CentOS-7 - SCLo rh
baseurl=https://mirrors.aliyun.com/centos/7/sclo/x86_64/rh/
# mirrorlist=http://mirrorlist.centos.org?arch=$basearch&release=7&repo=sclo-rh
gpgcheck=0
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-SCLo
完成后刷新 yum 缓存
yum repolist
yum clean all
yum makecache
之后开始正常流程
# 安装基础的 c++
sudo yum install libstdc++-devel -y
yum install -y gcc-c++
# 查看版本 应该是老的 485
g++ --version
# 安装新版本并切换
yum install -y devtoolset-11*
scl enable devtoolset-11 bash
# 再次查看版本
g++ --version
Java
- amd 架构
yum install -y java-1.8.0-openjdk-devel.x86_64
环境准备[ARM64]
CentOS7 在 ARM 架构的环境及其恶劣,AMD 至少还有阿里云有几乎完整的支持,ARM 连阿里云都缺胳膊少腿
-
CentOS7 ARM64(docker 版)
docker pull centos:centos7.9.2009 docker run -itd --name centos-arm centos:centos7.9.2009 bash强烈建议在自己机器上运行(可以方便的使用魔法,麻瓜理论上搭不起来环境)
另:魔法“释放速度”建议用快的,否在可能会在下载卡很久
替换 yum 源
为何这里提一嘴?因为 arm 架构的 yum 源有点不一样,amd 的网上一搜一大堆,这个还真不一定能找到。
由于没有安装 wget,所以我们需要现在宿主机拉下 Repo, 并替换原生的
mkdir tmp
cd tmp
wget https://mirrors.aliyun.com/repo/Centos-altarch-7.repo
docker cp Centos-altarch-7.repo centos-arm:/etc/yum.repos.d/CentOS-Base.repo
备齐编译环境
由于阿里的 centos-release-scl 源中没有收录 arm 架构的 c++,我们只能通过源码编译的方式来安装 c++ 。
别问我为啥不试试网易,清华源——自从上游仓库停止支持后,他们也停止支持了,现在唯一能用的只剩下阿里云了......
yum install libstdc++-devel -y
yum install -y gcc-c++
# 查看版本 应该是老的 485
g++ --version
yum install -y epel-release
yum install -y cmake3
sudo ln -s /usr/bin/cmake3 /usr/bin/cmake
yum groupinstall "Development Tools"
yum install -y bison flex texinfo gmp-devel mpfr-devel libmpc-devel
# 对,没错,我们用 git 去下,官网里直接推荐用 git,咱入乡随俗(能省一事就省一事)
# 这也是需要魔法的原因之一
yum install -y git
yum install -y make
C++ 11
git clone git://gcc.gnu.org/git/gcc.git
cd gcc
# 与 amd64 版本保持一致
git checkout releases/gcc-11.2.0
./configure --enable-languages=c,c++ --disable-multilib -prefix /usr
make -j$(nproc)
make install
# 这时候应该就完成了,查看下版本
gcc --version
关于
./configure ...... -prefix /usr的说明。在不指定安装前缀 /usr 的情况下,默认会把程序安装至 /usr/local/bin 目录下。
这时你会发现,
gcc --version与/usr/bin/gcc --version得到的结果不一致。然而由于你的 Cmake 以及原先安装的 gcc 在 /usr/bin 目录下,并且对于 Cmake 来说 /usr/bin 下的 gcc 优先级更高,这会导致后续在编译 DuckDB 的时候,依旧会使用 4.8.5 老版本的 gcc,最终导致编译失败。
为啥提一嘴,搜出来的 configure 相关攻略里没有加 prefix ,也没有相关说明 QAQ,导致最终编译 DuckDB 一直失败,我一度以为是 gcc 的版本在 arm 架构下要求更高,直到我一路向上,换上了 2025 年版本的 gcc 才开始怀疑是我 gcc 版本的问题。
归功于这个命令
cmake --system-information | grep CMAKE_C_COMPILER让我发现 cmake 在使用 cc 作为编译命令,然而使用
cc --version时,发现其 base 的 gcc 版本是 485,最终排查出上面的结论
Java
yum install -y java-1.8.0-openjdk-devel.aarch64
DuckDB
由于环境齐备,我们直接用 git 下 DuckDB 源码
git clone -b v1.2.2.0 https://github.com/duckdb/duckdb-java.git
## 查看当前所在 tag
cd duckdb-jdbc
git describe --tags --exact-match
接下来就可以愉快的编译啦~
编译
- 解压targz
tar -zxvf duckdb-java-1.2.2.0.tar.gz
- 打包(很久很久)
cd duckdb-java-1.2.2.0
make release
一般能在 build/release/duckdb_jdbc.jar 这个路径下找到打包后的结果
测试 & 安装
根据官方提供的命令测试 —— make test
(blob 阶段可能会等很久,没耐心可以略过)
ARM 64 架构 make test 时可能会遇到
undefined symbol: pthread_atfork报错这时需要调整 CMakeLists.txt 文件中的依赖
vi CMakeLists.txt # vim 命令搜索 CMAKE_DL_LIBS 关键词 /CMAKE_DL_LIBS # 在所在的括号中换行并加入下面的依赖 pthread最后 CMakeLists.txt 的那块代码应该长这样
target_link_libraries(duckdb_java PRIVATE duckdb-native ${CMAKE_DL_LIBS} pthread )重新编译
make clean make release打包完成后,重新进行 make test
将打好的包放到本地进行安装
# 从服务器把包捞回来
scp root@xx.xx.xx.xx:~/duckdb-java-1.2.2.0/build/release/duckdb_jdbc.jar ./
# 安装到 maven 仓库
mvn install:install-file -Dfile=./duckdb_jdbc.jar -DgroupId=org.duckdb -DartifactId=duckdb_jdbc -Dversion=1.2.2.0-glibc2.17 -Dpackaging=jar
打包进简易的项目中进行测试:
- 1.2.2.0 依赖下的包运行会报错
[root@iZbp1045yp8jiys3baq2alZ ~]# java -jar ./duckdbUse-2.0-SNAPSHOT.jar
Exception in thread "main" java.lang.UnsatisfiedLinkError: /tmp/libduckdb_java6604742508229353124.so: /lib64/libm.so.6: version `GLIBC_2.23' not found (required by /tmp/libduckdb_java6604742508229353124.so)
at java.lang.ClassLoader$NativeLibrary.load(Native Method)
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1934)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1817)
at java.lang.Runtime.load0(Runtime.java:782)
at java.lang.System.load(System.java:1100)
at org.duckdb.DuckDBNative.<clinit>(DuckDBNative.java:58)
at org.duckdb.DuckDBConnection.newConnection(DuckDBConnection.java:52)
at org.duckdb.DuckDBDriver.connect(DuckDBDriver.java:48)
at java.sql.DriverManager.getConnection(DriverManager.java:664)
at java.sql.DriverManager.getConnection(DriverManager.java:208)
at top.saycode.SimpleDuckDbUse.main(SimpleDuckDbUse.java:28)
- 依赖 1.2.2.0-glibc2.17 的则一切正常
[root@iZbp1045yp8jiys3baq2alZ ~]# java -jar ./duckdbUse-1.0-SNAPSHOT.jar
id
更优雅的方式
照常理这个话题到此应该就结束了,然而后续将项目中的依赖替换成我自建的包时又遇到了两个问题:
- 替换后,由于该包只在 Linux amd64 上打的,Linux arm64 / MacOS / Windows 上将无法使用,虽然这个包一定程度上契合了客户,但是对后续的维护会带来很多麻烦
- 在项目中复杂的架构/依赖下,我们自己打的包似乎依旧无法正常运行(java 程序总会从错误的地方寻找 native api 包)
我似乎应该寻找一个影响更小的方式
在仓库中找到 1.2.2.0 (官方版本)的 jar 包,解压,看看里面有些啥
# 仓库位置因人而异
cd ~/.m2/repository/org/duckdb/duckdb_jdbc/1.2.2.0/
# 创建个临时文件夹 用于解压
mkdir tmp
cp duckdb_jdbc-1.2.2.0.jar ./tmp
jar -xf duckdb_jdbc-1.2.2.0.jar
# 查看
ls
# META-INF
# duckdb_jdbc-1.2.2.0.jar
# libduckdb_java.so_linux_amd64
# libduckdb_java.so_linux_arm64
# libduckdb_java.so_osx_universal
# libduckdb_java.so_windows_amd64
# org
从上方的查询结果不难看出,duckdb 对应的 c++ 的代码应该是在 libduckdb_java.so_linux_xxx 下
那么解决方案就明朗了,我只需要将在旧 Linux 系统下编译出 jar 包中的 libduckdb_java.so_linux_amd64 替换到官方 jar 包下就能解决上面的问题(其实在 build/release 目录下会直接提供 libduckdb_java.so_linux_amd64 文件)
由于所需的包和关键命令都已提到,替换过程略...
对替换好后的 jar 包重新打包并安装
jar -cfm duckdb_jdbc-1.2.2.0-glibc2.17.jar META-INF/MANIFEST.MF *
mvn install:install-file -Dfile=./duckdb_jdbc-1.2.2.0-glibc2.17.jar -DgroupId=org.duckdb -DartifactId=duckdb_jdbc -Dversion=1.2.2.0-glibc2.17 -Dpackaging=jar
为保证用户能正常在 arm64 的系统架构上运行,应当在 arm 架构的 linux 系统上再一次构建 DuckDB jdbc 并替换上方的 libduckdb_java.so_linux_arm64