容我先吐槽一下某"互联网大公司", 不然难解我心头之恨. 现在是2023年, CentOS6是2011年发布的, 我2011年还是上小学的小屁孩, 十多年过去了, 我大学毕业参加工作, 还能被CentOS6折磨到破防, 这是怎样一个神奇妙妙旅程. 更别提还有用Linux 4.18强行装到CentOS7上的离谱行为, 内核用的是新的, Glibc用的是旧的, 我真想不明白图什么. 图稳定? CentOS, 不过一个雨林木风版的RHEL, 没了背后的红帽, 好像有问题自己真能修一样, 乐.
事情是这样的, 线上机器有很大一批用的还是CentOS6, Glibc2.10的系统环境. 由于一批机型磁盘大小比较小, 而程序又依赖RocksDB存储数据, 恰好最近数据变动比较多, LSM占用了太多空间导致磁盘容量告急.
为了让程序不至于完全写满硬盘导致服务不可用, 只能想办法先手动合并一次RocksDB, 清除无用数据释放空间.
什么? 你问我为什么程序自身没有主动合并的逻辑? 为什么没有配置Rocksdb主动合并? 我只是个接盘的倒霉蛋, 我怎么知道.
既然程序自身没有这个逻辑, 我的计划是自己简单写一个小工具, 调用Rocksdb接口, 触发一次手动合并即可. 但是新的问题出现了, 现在几乎找不到CentOS6的软件源了, 我没办法配置一套与服务器一致的开发环境.
在网上搜索的过程中, 看到了Uber使用zig提供的工具链, 可以实现完全"封闭"的编译链接过程, 完全不依赖外部环境, zig可以把自己伪装成一个c/c++的编译器, 并且允许你指定任何编译平台目标, 包括CPU平台, 操作系统, 标准库版本.
这我也是试过的, 但是在glib2.10面前, 都是弟弟. zig的c/c++编译功能实际上就是封装了clang, 而clang++的C++标准库libc++, 又直接依赖glibc2.15的一个接口, 此路不通.
那么剩下就没什么可选的了, 静态编译, 启动! 如果出现了老内核无法支持的系统调用, 那我也没什么好办法了, 人和代码总要有一个能跑, 代码不行的话, 那就是我了.
但是, 试过就知道, 想要静态链接glibc库, 问题很多, 你会见到各种莫名其妙的问题, 我这里就不一一说明了, 总之难度很高.
用glibc终究是有极限的jojo, 所以我选择, 去特喵的glibc!
MUSL
musl是除glibc以外另一套可以运行在Linux环境的C语言标准库(尝试过静态编译Rust的朋友应该并不陌生), 主打的就是一个轻量, 便携, 用来静态编译C程序非常的方便.
性能会差一些
如果是C代码, 很简单, 下载musl的源代码, 编译, 安装. 它会给你一个包装后的gcc, 名为musl-gcc. 通过它编译出的程序, 会将对C标准库的依赖从glibc变为musl, 让整个程序静态化, 就像Go一样, 程序自身不包含任何依赖, 它自己只需要与内核交互就能完成功能.
但我们知道Rocksdb是C++的库, 它会依赖C++标准库即libstdc++. 而libstdc++又直接依赖glibc. 即使强行替换glibc为musl, 链接阶段也会看到各种找不到符号的报错.
冲动的同学可能会转头自己去编译一套基于musl的libstdc++去了, 不过不要急.
在musl的Wiki里面, 我翻到了这个: https://musl.cc/
这是一个社区维护的基于musl的交叉编译工具链, 可以支持的平台有很多, 并且里面的gcc/g++等工具都是经过修改的, 会默认使用musl(但是默认是动态链接musl, 设置为静态的话, 简单加一个链接参数-static即可).
到这里, 我们的任务就差不多了. 下载, 配置PATH变量. 将编译器设置为 x86-64-musl-g++, 重新编译一份rocksdb的静态库, 然后继续用这套编译器编译自己的代码, 最后, 链接(别忘了 -static).
你就会得到一个很大的, 整个是静态链接的C++程序(可以开启链接时优化以及upx, 能减少不少体积).
可能存在的问题
Musl相对于默认的Glibc, 关于内存管理的各种函数, 性能要差一些. 如果必要, 可以考虑自己把memcpy, memset这些实现从glibc中摘出来手动链接到自己的程序上, malloc/free也是, musl的内存分配应该是有一个全局的大锁, 并发起来性能会很差, 可以考虑链接tcmalloc或者mimalloc, 会好很多.
Discussion