ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ARM架构鲲鹏+麒麟V10下ClickHouse 21.8源码编译与避坑

ARM架构鲲鹏+麒麟V10下ClickHouse 21.8源码编译与避坑

简介:面向国产化服务器环境的大数据与运维人员,这份 ClickHouse 21.8.14 安装包针对华为鲲鹏920(aarch64)与麒麟V10完成编译适配,解决在自主可控平台部署OLAP数据库时源码编译难、依赖复杂的问题。压缩包共20个文件、约115.69MB,包含install.sh与uninstall.sh一键安装卸载脚本、config.xml和users.xml配置文件、clickhouse-server/client等核心可执行组件及systemd服务单元,覆盖部署、客户端连接与服务托管所需。已有2150人学习下载,说明其在信创场景中具有参考价值。读者可据此快速搭起可运行的列式数据库实例,省去交叉编译与库依赖排查,并借助现成配置理解端口、数据目录和用户权限设置,适合需要开展PB级数据分析验证、学习ClickHouse运维或搭建国产化测试环境的技术人员。

1. 从一份 ARM 安装包说起:为什么鲲鹏 + 麒麟 V10 必须自己编

很多人在鲲鹏机器上装 ClickHouse,第一反应是去找现成 rpm,翻遍官方仓库只看到 x86_64 的包,才回头去搜「clickhouse 鲲鹏 麒麟V10」这类关键词。找到一份标注「clickhouse_21.8.14 安装包(鲲鹏服务器 + 麒麟 V10 操作系统编译)」的资源,才算把这条路走通。它解决的事情很朴素:在国产化 aarch64 环境里,官方没有可直接落地的二进制,只能拿源码完整编一遍 21.8.14 这条 LTS 分支。难点不在命令本身,而在三个条件叠一起:麒麟 V10 自带的 gcc 版本低于 21.8 的下限、鲲鹏机器的内核页大小可能不是 4K、glibc 又偏保守。适合手上有一批鲲鹏机器、要做私有化交付、又不愿意把版本退回到更老分支的运维和交付工程师。

2. 编译前的平台体检:把 aarch64 的坑先在纸面上排掉

在鲲鹏上编 ClickHouse,最贵的成本不是 CPU 时间,是「编了三个小时才发现编译器版本不对」。所以第一步不是 clone,是把机器底细摸清楚。这一步花十分钟,后面能省一整晚。

2.1 三件事必须先确认:架构、页大小、glibc

uname -m决定你下载的到底是 aarch64 源码还是 x86_64 源码,getconf PAGESIZE决定后面 jemalloc 怎么配,ldd --version决定预编译包拿过来能不能直接跑。这三条我一般写成一段固定脚本,每台新机器先跑一遍,结果贴到交付记录里。

#!/bin/bash # 平台体检:新机器第一件事 echo "===== 架构 =====" uname -m # 期望 aarch64 uname -r # 内核版本,用于判断麒麟 V10 的具体 SP echo "===== 页大小 =====" getconf PAGESIZE # 4K 或 64K,决定 jemalloc 参数 getconf PAGE_SIZE echo "===== C 运行库 =====" ldd --version | head -1 # 期望 glibc 2.28 左右 echo "===== 编译器 =====" gcc --version | head -1 g++ --version | head -1 echo "===== 构建工具 =====" cmake --version | head -1 # 21.8 要求 3.14 以上 ninja --version echo "===== 资源 =====" free -g # 内存,链接阶段至少 16G 才能稳 nproc # 核数,决定 ninja -j df -h /data /opt

输出里有三个数值得盯:

  • getconf PAGESIZE如果是 65536,说明这台鲲鹏用的是 64K 页,后面 jemalloc 必须显式指定LG_PAGE=16,否则编出来的二进制一启动就崩,这是后面避坑章要专门讲的一条。
  • gcc --version如果是 7.x,基本可以断定编不了 21.8,麒麟 V10 的默认源里常见就是 7.3.1,而 ClickHouse 21.8 的 CMake 里有硬性判断,低于 GCC 10 直接报错退出。
  • free -g的 total 如果只有 8G,链接clickhouse这个单体二进制时大概率被 OOM Killer 干掉,返回码 137,日志里只留一行 Killed,非常像玄学问题。

2.2 编译器版本是第一个硬门槛

ClickHouse 21.8 对编译器的要求写在 CMake 的检测逻辑里,GCC 低于 10 会直接失败,提示大意是「GCC version must be at least 10」。麒麟 V10 默认的 gcc 7.3.1 差得不是一点半点,这条路只有两个走法。

第一个走法是 devtoolset。麒麟 V10 的源里如果带devtoolset-11或gcc-toolset-11,直接装,然后用 scl 进环境。装完之后gcc --version显示 11.x,但注意一定要在 scl 环境里跑 cmake,否则 CMake 抓到的还是系统的 7.3.1。

# 走 devtoolset 路线 yum install -y devtoolset-11-gcc devtoolset-11-gcc-c++ devtoolset-11-binutils # 进入 scl 环境后再确认版本 scl enable devtoolset-11 bash gcc --version | head -1 # 必须显示 11.x which gcc # 应在 /opt/rh/devtoolset-11/root/usr/bin 下

第二个走法是自编 gcc。源里没有 devtoolset 的时候,我一般编一个 gcc 11 装到/opt/gcc-11,不覆盖系统 gcc,通过CC/CXX环境变量指过去。好处是不污染系统,坏处是要多花四十分钟编 gcc 本身,而且libstdc++的版本要一起带进去。

提示:不管走哪条路,编完之后g++ -v里那行--with-gxx-include-dir指向的头文件目录必须存在,否则编到一半会出现「找不到 bits/c++config.h」这种看着莫名其妙的错误。

还有一条常被忽略的路线:用 clang。如果源里能装到 clang 12 以上,直接用 clang 编 ClickHouse 反而更贴近上游默认配置,编出来的二进制一般也更小。代价是 clang 和 libstdc++ 的搭配要试,有的环境要额外装libstdc++-static。

2.3 依赖清单与麒麟 V10 源里的版本差异

依赖分两层:一层是构建工具,一层是运行时库。构建工具缺了直接编不动,运行时库缺了是编过了但跑不起来。我习惯一次性把两层都装上,省得来回折腾。

# 构建工具 + 运行时依赖,麒麟 V10 用 yum yum install -y \ git cmake ninja-build ccache \ python3 python3-devel \ zlib-devel openssl-devel \ libicu-devel readline-devel ncurses-devel \ libtool automake autoconf \ bzip2 lz4-devel zstd-devel \ libstdc++-static glibc-devel

几个容易出问题的点:

  • ccache一定要装。ClickHouse 全量编译的对象文件数量在几千个量级,有 ccache 之后改一行配置重编只要几分钟,没有的话每次都是几个小时起步。
  • cmake版本。麒麟 V10 源里的 cmake 常见是 3.17 左右,够用;如果低于 3.14,得自己升,升的时候别卸系统 cmake,装到/usr/local下再调 PATH。
  • libstdc++-static这个包在某些源里叫libstdc++-static,有些叫gcc-static,装不上不影响动态链接,但后面想编成完全静态的二进制就会卡住。
  • zstd-devel和lz4-devel如果源里没有,不用强求,ClickHouse 的 contrib 里有内置版本,只是编译时间会长一点。

2.4 源码和 contrib 子模块,一次拉全

ClickHouse 的仓库结构是主仓库 + 几十个 git submodule,全部第三方库都在contrib/下面。21.8 这个版本尤其依赖子模块的完整性,少一个就是编译到某个阶段突然报头文件找不到。我一般不用浅克隆拉主仓库,因为 tag 对不上会很难查。

# 拉主仓库,指定 21.8.14 对应的 LTS tag git clone https://github.com/ClickHouse/ClickHouse.git cd ClickHouse git checkout v21.8.14.5-lts # tag 名以仓库实际为准,用 git tag -l 'v21.8.14*' 确认 # 拉子模块,这一步最容易断,断了就重跑,别怕重复执行 git submodule sync --recursive git submodule update --init --recursive

子模块拉完之后做一次自检,看数量对不对:

# 统计子模块状态,全部应为已初始化 git submodule status --recursive | wc -l git submodule status --recursive | grep '^-' | head

第二条命令如果输出为空,说明所有子模块都拉下来了;如果有输出,那几行就是没拉成功的,单独进去git submodule update --init <路径>补上。这一步不做检查直接编,最常见的表现是编到contrib/xxx时报fatal error: xxx.h: No such file or directory,然后你以为是依赖没装,实际上是源码不全——这个坑我在第 5 章还会再提一次。

3. 配置与编译:cmake 参数怎么定、ninja 怎么跑

环境过了之后,剩下的是参数问题。ClickHouse 的 CMake 选项有上百个,但真正影响鲲鹏 + 麒麟这条线的就那么十来个,其余用默认值。这一章把每个开关为什么这么设讲清楚,照着抄能跑通,改的时候也知道改哪。

3.1 cmake 开关逐条过

先建 build 目录,别在源码根目录直接 cmake,污染源码树后面很难清理。参数我一般写成一行,方便复用和贴到交付文档里。

mkdir -p build && cd build cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/opt/clickhouse \ -DCMAKE_C_COMPILER=$(which gcc) \ -DCMAKE_CXX_COMPILER=$(which g++) \ -DCMAKE_CXX_FLAGS="-march=armv8-a -fno-omit-frame-pointer" \ -DENABLE_TESTS=OFF \ -DENABLE_BENCHMARKS=OFF \ -DENABLE_EMBEDDED_COMPILER=OFF \ -DUSE_INTERNAL_SSL_LIBRARY=ON \ -DSPLIT_SHARED_LIBRARIES=OFF \ -DUSE_JEMALLOC=ON \ -DJEMALLOC_LG_PAGE=16

逐条说明,这几项是这套环境下必须显式写的:

  • CMAKE_BUILD_TYPE=Release:不加这个会编成 Debug,二进制大两三倍,运行时性能差一个数量级,而且链接阶段更容易 OOM。
  • CMAKE_CXX_FLAGS="-march=armv8-a":不要写-march=native。鲲鹏 920 支持 ARMv8.2 的部分扩展,在编译机上用 native 编出来的二进制换一台老一点的鲲鹏就跑不起来,报Illegal instruction。交付场景一律用armv8-a这个基线。
  • ENABLE_TESTS=OFF和ENABLE_BENCHMARKS=OFF:测试和压测代码占了相当一部分编译量,交付包不需要,关掉能省不少时间。
  • ENABLE_EMBEDDED_COMPILER=OFF:这个开关控制 LLVM JIT 编译器,开了会编一整个 LLVM 进去,编译时间翻倍,二进制约增加几百兆。只有确实要用表达式 JIT 加速才开。
  • USE_INTERNAL_SSL_LIBRARY=ON:用 contrib 里自带的 SSL 库,不链系统的 openssl。麒麟 V10 系统 openssl 版本和 ClickHouse 期望的经常对不上,链系统库是后面运行时报符号缺失的高发区。
  • SPLIT_SHARED_LIBRARIES=OFF:保持单一二进制。开了之后会拆成一堆.so,部署时容易漏文件。
  • JEMALLOC_LG_PAGE=16:这个就是 64K 页对应的参数,2^16 = 65536。如果体检时getconf PAGESIZE是 4096,这项要改成 12 或者干脆去掉用默认值。这一步搞错,编出来就是能启动但立刻 core。

cmake跑完之后,输出末尾要检查三样东西:编译器路径是不是 devtoolset 里那个、CMAKE_BUILD_TYPE是不是 Release、有没有出现红色的Could NOT find但被标成必需项。前两个是配置问题,第三个是依赖问题,都在这一步解决掉,别拖到编译阶段。

3.2 ninja 并行度和内存的平衡

配置过了就开编。ClickHouse 用的是 ninja,并行度-j设成核数,但有个前提:内存要跟得上。经验值是每并行一个编译任务大约需要 1.5~2 GB 内存,32 核机器理论上要 64G 才完全跑满。

# 核数够但内存一般的机器,用一半核数 ninja -j 16 clickhouse # 内存充裕(64G+)的机器可以直接满核 ninja -j $(nproc) clickhouse

这里有个细节:ninja clickhouse只编clickhouse这一个目标,会跳过所有测试二进制和工具,比直接ninja省很多。如果后期需要clickhouse-client、clickhouse-local这些,不用分别编——21.8 是单一多合一二进制,编出来的clickhouse通过不同的 argv[0] 或者子命令区分角色,一个文件全包。

编之前先把 ccache 配好,不然重编一次就白等:

# ccache 配置,放在编译前 ccache -M 20G # 缓存上限,编 ClickHouse 20G 起步 ccache -z # 清零统计,方便看命中率 export CCACHE_DIR=/data/ccache # 放数据盘,别放根分区

编的过程中用另开一个终端看进度和资源:

# 监控编译资源,判断是不是要降并发 watch -n 5 'free -g; echo ---; uptime'

ninja的输出是[当前/总数],总数在 21.8 上大概是三四千个编译单元。鲲鹏 920 32 核、-j 16的情况下,全新编译大概两到四小时;有 ccache 热缓存时,改配置重编往往十分钟内。这个时间量级心里要有数,中途看着不动别急着 Ctrl+C。

3.3 产物校验与裁剪

编完之后第一件事是确认二进制真的能跑,而不是先打包。

# 产物位置与基本信息 ls -lh programs/clickhouse file programs/clickhouse # 应显示 ELF 64-bit LSB, ARM aarch64 ./programs/clickhouse --version # 看构建选项,确认 jemalloc、SSL 来源是否符合预期 ./programs/clickhouse local --query \ "SELECT name, value FROM system.build_options WHERE name IN \ ('VERSION_FULL','USE_JEMALLOC','USE_SSL','CXX_COMPILER')"

system.build_options这张表是排查「编出来的东西和预期不一致」的黑匣子,里面记录了编译时实际生效的选项。如果USE_JEMALLOC显示 0,说明 jemalloc 被跳过了,要么参数写错,要么 contrib 里的 jemalloc 编译失败被静默降级。这个一定要在打包前确认。

第二件事是裁剪。Release 版二进制里还带着符号表,体积很大,交付前 strip 一下:

# 保留一份带符号的用于排查,交付用 strip 过的 cp programs/clickhouse /data/clickhouse-debug strip --strip-unneeded programs/clickhouse ls -lh programs/clickhouse

strip 之后体积通常能小一半以上。带符号的那份留在编译机上,线上如果出现 core dump,用gdb /data/clickhouse-debug core.xxx才能看到函数名,不然就是一堆地址。这是血泪经验,别图省事把带符号的全删了。

4. 麒麟 V10 上的运行配置:目录、服务、最小可用参数

编出来只是拿到一个文件,能不能稳定跑起来是另一回事。这一章按「目录 → 服务 → 配置 → 验证」的顺序走一遍,配完就是一个能接业务的最小可用实例。

4.1 目录布局与多合一二进制的分发

21.8 是单一二进制,服务端、客户端、本地模式都是同一个文件,靠软链接区分。所以目录结构要提前定好,后面升级只换一个文件。

# 目录布局 mkdir -p /opt/clickhouse/bin mkdir -p /etc/clickhouse-server mkdir -p /var/lib/clickhouse mkdir -p /var/log/clickhouse-server # 安装二进制并建软链 cp programs/clickhouse /opt/clickhouse/bin/clickhouse chmod 755 /opt/clickhouse/bin/clickhouse cd /opt/clickhouse/bin ln -sf clickhouse clickhouse-server ln -sf clickhouse clickhouse-client ln -sf clickhouse clickhouse-local # 建运行用户 useradd -r -s /sbin/nologin clickhouse chown -R clickhouse:clickhouse /var/lib/clickhouse /var/log/clickhouse-server /etc/clickhouse-server

软链接这一步不是可选的。后面 systemd 单元里写clickhouse-server,程序靠 argv[0] 判断自己该当服务端还是客户端,链接名写错,启动时它就当成客户端跑,然后立刻退出,日志里什么提示都没有,看起来像「启动即挂」。

配置文件从源码里拷一份模板,改比从零写快得多:

# 从源码拷默认配置模板 cp ../programs/server/config.xml /etc/clickhouse-server/config.xml cp ../programs/server/users.xml /etc/clickhouse-server/users.xml chown clickhouse:clickhouse /etc/clickhouse-server/*.xml

4.2 systemd 单元和内核参数

麒麟 V10 用 systemd,写一个单元比手工 nohup 靠谱。单元里有两行Limit*是必须加,不加会在日志里刷一堆设置失败。

[Unit] Description=ClickHouse Server (aarch64 build) After=network-online.target Wants=network-online.target [Service] Type=simple User=clickhouse Group=clickhouse Restart=always RestartSec=5 LimitNOFILE=1048576 LimitMEMLOCK=infinity LimitNPROC=1048576 ExecStart=/opt/clickhouse/bin/clickhouse-server \ --config-file=/etc/clickhouse-server/config.xml [Install] WantedBy=multi-user.target

关键参数解释:

  • LimitNOFILE=1048576:ClickHouse 每个 MergeTree 分区、每个连接都占文件描述符,默认的 1024 远远不够,跑起来很快报Too many open files。
  • LimitMEMLOCK=infinity:ClickHouse 想把内存锁住防止换页,不加这行会持续输出Cannot set RLIMIT_MEMLOCK。虽然不是致命错误,但日志会很脏。
  • Restart=always:这个要配合下面的配置改一起考虑。如果配置有语法错误,always会导致无限重启刷日志,所以第一次启动建议先手动跑,确认没问题再 enable。

除了 systemd,/etc/security/limits.conf也顺手加上,防止用户从 shell 直接起进程时权限不够:

# /etc/security/limits.conf 追加 cat >> /etc/security/limits.conf <<'EOF' clickhouse soft nofile 1048576 clickhouse hard nofile 1048576 clickhouse soft memlock unlimited clickhouse hard memlock unlimited EOF

麒麟 V10 如果开了 SELinux 或者防火墙,数据目录和端口要做对应放行,/var/lib/clickhouse的上下文不对会报权限拒绝,日志里写的是Permission denied,看着像文件权限问题,实际是 SELinux 标签。

4.3 config.xml 最小改法

默认config.xml是给通用场景用的,跑在国产化机器上要改几处。改动不多,但每一处都影响能不能跑起来。

配置项默认值鲲鹏/麒麟建议值原因
listen_host::10.0.0.0或具体内网 IP默认只监听本地,外部连不上
max_server_memory_usage0(自动)按物理内存 70% 手写ARM 上内存统计口径有差异,手写更稳
path/var/lib/clickhouse/数据盘路径根分区扛不住 MergeTree
tmp_path/var/lib/clickhouse/tmp/同数据盘大查询落临时文件
logger/leveltraceinformationtrace 日志量太大,磁盘压力大
mark_cache_size自动显式写 2~4G显式设置避免启动时按机器内存乱算

用 sed 批量改也行,但config.xml里同名节点不少,我还是倾向直接编辑。改完之后用 XML 工具校验一下,语法错误在启动时只报一行,很难定位:

# XML 语法校验,能过说明标签闭合没问题 xmllint --noout /etc/clickhouse-server/config.xml && echo "config ok" xmllint --noout /etc/clickhouse-server/users.xml && echo "users ok"

users.xml里至少要配一个可远程访问的账号,默认default用户是空密码 + 只允许本地。交付环境里我一般新建一个业务账号,密码用clickhouse-client的PASSWORD字段做 sha256,而不是明文:

# 生成密码的 sha256 密文,填到 users.xml 的 password_sha256_hex 字段 PASSWORD=$(echo -n 'YourPassw0rd' | sha256sum | tr -d ' -') echo $PASSWORD

4.4 启动与四条冒烟测试

配置改完先手动起一次,别急着 systemctl enable,前台能看到完整报错。

# 手动前台启动,看日志 sudo -u clickhouse /opt/clickhouse/bin/clickhouse-server \ --config-file=/etc/clickhouse-server/config.xml

看到Listening for connections和Ready for connections两行才算真的起来了。然后在另一个终端跑四条测试:

# 1. 版本与架构 /opt/clickhouse/bin/clickhouse-client --query "SELECT version(), hostName()" # 2. 构建选项,确认 jemalloc 和 SSL 来源 /opt/clickhouse/bin/clickhouse-client --query \ "SELECT name, value FROM system.build_options WHERE name IN ('VERSION_FULL','USE_JEMALLOC')" # 3. 建表写入读回,走一遍 MergeTree 完整链路 /opt/clickhouse/bin/clickhouse-client --multiquery <<'EOF' CREATE DATABASE IF NOT EXISTS smoke; CREATE TABLE smoke.t (id UInt64, ts DateTime, v String) ENGINE = MergeTree ORDER BY id; INSERT INTO smoke.t SELECT number, now(), toString(number) FROM numbers(10000); SELECT count(), max(id) FROM smoke.t; EOF # 4. 本地模式,不依赖服务端,验证二进制本身 /opt/clickhouse/bin/clickhouse-local --query "SELECT sum(number) FROM numbers(1000000)"

第三条是关键,它同时验证了元数据写入、数据落盘、后台 merge 三件事。第四条的clickhouse-local完全绕开服务端,如果服务端起不来但 local 能跑,说明二进制没问题,问题在配置或者权限;如果 local 也崩,那就是编译阶段的问题,回去查 jemalloc 的页大小参数。

5. 避坑记录:五个在 aarch64 上真实翻车的点

这一章全部是现象 → 原因 → 解决的实录,按出问题的时间顺序排。前面四章是正常路径,这一章是正常路径走不通时该看哪。

5.1 链接阶段被 OOM Killer 干掉

现象:ninja跑到最后几十个任务,卡在Linking CXX executable programs/clickhouse,然后进程直接消失,没有任何编译错误,echo $?是 137。

原因:ClickHouse 是单体二进制,链接时要把所有目标文件同时映射进内存,这一步的峰值内存在 8~12G 量级。8G 内存的鲲鹏虚拟机在这一步必然触发内核 OOM Killer,而 OOM Killer 只往 dmesg 里写一行,编译输出里什么都没有,非常像玄学。

解决:三条路按优先级排。一是加 swap,16G 起步,swapon之后重跑链接;二是降并发到-j 4,虽然总时间变长,但同一时刻只有一个链接任务在跑;三是换机器,编译机内存 32G 以上是最省心的。检查方法:

# 确认是不是 OOM,看最近有没有被杀记录 dmesg -T | grep -i -E 'killed process|out of memory' | tail -5 free -g

5.2 jemalloc 在 64K 页上直接崩

现象:编译完全成功,clickhouse local一跑就 coredump,或者服务端启动后几秒内退出,日志里出现Unsupported system page size或者干脆什么都没有。

原因:getconf PAGESIZE是 65536,而 contrib 里的 jemalloc 默认按 4K 页(LG_PAGE=12)编译,运行时它去mmap一块按 4K 对齐的区域,内核按 64K 对齐返回,两者对不上就断言失败。

解决:cmake 加-DJEMALLOC_LG_PAGE=16(64K 对应 2 的 16 次方)。如果已经编完了,不用全量重编,改参数后重新 cmake + ninja,ccache 能命中大部分目标文件,十几分钟就好。实在不想折腾,用-DUSE_JEMALLOC=OFF退回 glibc malloc,能跑,但高并发下性能会掉,属于「后悔药」不是「正解」。

# 确认页大小 getconf PAGESIZE # 65536 就对应 LG_PAGE=16,4096 对应 12 # 重新配置,只改这一项 cd build cmake .. -DJEMALLOC_LG_PAGE=16 ninja -j 16 clickhouse

5.3 SSL 和 zlib 串到系统库

现象:编译通过,启动时报undefined symbol: SSL_xxx或者version 'OPENSSL_1_1_1' not found,也可能跑了一段时间在建立连接时崩。

原因:config.xml没指定,但编译时链接器找到了系统/usr/lib64下的 openssl 1.0.x 或者别的版本,而 ClickHouse 21.8 的代码是按 1.1.1 的 API 写的。麒麟 V10 的源里可能有多个 openssl 并存。

解决:编译时显式用内置 SSL 库,-DUSE_INTERNAL_SSL_LIBRARY=ON,并且编译完成后检查链接依赖:

# 看二进制依赖了哪些动态库 ldd /opt/clickhouse/bin/clickhouse | grep -E 'ssl|crypto|z\.so' # 如果出现系统 libssl,说明内置库没生效,回 cmake 检查参数

理想情况下这里不应该看到libssl.so。如果看到,重新 cmake 时把-DOPENSSL_ROOT_DIR指到 contrib 下的目录,或者干脆重新拉一次子模块,很可能是contrib/openssl没拉全。

5.4 子模块版本对不上,报奇怪的编译错

现象:编到某个 contrib 库时,报某个结构体没有成员、某个宏未定义,或者 CMake 阶段报Could NOT find Boost之类,但明明装了。

原因:主仓库的 tag 和contrib/子模块的 commit 是绑定的,如果 clone 的时候网络断了导致部分子模块停在旧的 commit,或者有人手动改过某个子模块的 HEAD,就会编译到「接口对不上」的版本组合。

解决:全部重新同步,别单独修某一个:

git submodule sync --recursive git submodule update --init --recursive --force git submodule status --recursive | grep '^-' # 应为空

--force会丢弃子模块里的本地改动。如果确实改过某些第三方库做适配,先git diff存一份再执行,这个坑我踩过一次,改了三天的补丁被--force冲掉,只能重来。

5.5 mlock 失败和时钟跳变

现象:服务能起,但日志里持续刷Cannot set RLIMIT_MEMLOCK;或者更隐蔽的一种,写入数据后查询偶尔查不到最新分区,日志里有Timestamp of part is in the future。

原因:前一个是 systemd 单元没设LimitMEMLOCK=infinity,进程拿不到锁内存权限;后一个是虚拟机时钟漂移,NTP 没开,MergeTree 用时间戳判断分区的可见性,系统时间往前跳一下,新写的分区就被判成「未来数据」而不可见。

解决:

# 检查内存锁限制 ulimit -l # 应为 unlimited # 时间同步,麒麟 V10 一般用 chronyd systemctl enable --now chronyd chronyc sources -v timedatectl set-timezone Asia/Shanghai # 确认没有未来时间戳的分区 /opt/clickhouse/bin/clickhouse-client --query \ "SELECT table, partition, modification_time FROM system.parts WHERE active AND modification_time > now()"

时钟这条最阴,因为它在测试环境不出现,上线跑几天才冒出来。交付前把 chronyd 配好,是成本最低的后悔药。

6. 编译产物的分发与复现验证

一台机器编完,剩下的几十台不用重编,把产物打包分发就行。但分发之前有个动作我每次都做,就是先在编译机上把包解开、在干净目录里跑一遍,确认不依赖编译机上的残留库。

打包只打三样东西:二进制、配置模板、一份版本说明。数据目录和日志目录在目标机上建,别打进去。

# 打交付包,文件名带上架构和系统标识,避免和 x86 包混 cd /data/pkg mkdir -p clickhouse-21.8.14-aarch64/{bin,etc,scripts} cp /opt/clickhouse/bin/clickhouse clickhouse-21.8.14-aarch64/bin/ cp /etc/clickhouse-server/config.xml clickhouse-21.8.14-aarch64/etc/ cp /etc/clickhouse-server/users.xml clickhouse-21.8.14-aarch64/etc/ tar -czf clickhouse-21.8.14-aarch64-kylin10.tar.gz clickhouse-21.8.14-aarch64/ sha256sum clickhouse-21.8.14-aarch64-kylin10.tar.gz > clickhouse-21.8.14.sha256

目标机上的解包和验证写成一段脚本,和包一起发过去,落地的人照着跑就行。这段脚本里有两条验证是必须的:一条查动态库依赖有没有指向编译机特有的路径,一条查 jemalloc 的实际状态。

#!/bin/bash # 目标机落地脚本,解包后先验证再装 set -e tar -xzf clickhouse-21.8.14-aarch64-kylin10.tar.gz cd clickhouse-21.8.14-aarch64 # 1. 检查动态库依赖,出现 not found 说明目标机缺运行时库 ldd bin/clickhouse | grep 'not found' && { echo "缺库,先装依赖"; exit 1; } # 2. 检查架构,防止把 x86 包拷过来 file bin/clickhouse | grep -q 'ARM aarch64' || { echo "架构不对"; exit 1; } # 3. 本地模式跑一条聚合,验证二进制本身能执行 ./bin/clickhouse local --query "SELECT count() FROM numbers(1000000)" # 4. 建软链、装目录、刷配置(略) echo "check passed"

第 3 条是我最看重的一步。它不依赖任何配置文件、不依赖服务端、不占端口,纯粹验证二进制在这个内核上能不能跑起来。如果这条过不了,后面配什么都白搭——最常见的就是目标机页大小和编译机不一致,二进制一执行就崩。所以落地脚本里这条要放在所有安装动作之前,先验证再动手。

版本核对也别靠肉眼。把system.build_options里的关键项导出来,和编译机上的记录做 diff,能一次性看出 jemalloc、SSL、编译器版本有没有在打包过程中被动过:

./bin/clickhouse-client --query \ "SELECT name, value FROM system.build_options ORDER BY name FORMAT TSV" \ > build_options_target.tsv diff build_options_compile.tsv build_options_target.tsv && echo "构建项一致"

从那以后我每次交付这种自编译包,都强制走一遍「干净目录解包 + ldd 检查 + clickhouse local 冒烟 + build_options diff」这四步,哪怕时间再紧也不跳。跳过这四步省下的二十分钟,后面往往要用一整晚在客户现场排查为什么起不来。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进