TDengine 中 fast-lzma2 的 Conan 集成指南:从包构建到压缩 API 实战
【免费下载链接】TDengineHigh-performance, scalable time-series database designed for Industrial IoT (IIoT) scenarios项目地址: https://gitcode.com/GitHub_Trending/tde/TDengine
导读
本文围绕 TDengine 仓库中 conan/fast-lzma2 这一 Conan 包配方展开,系统讲解 Fast LZMA2(v1.0.1)这一优化型 LZMA2 压缩库如何被打包、构建、测试并集成进 TDengine 的 CMake 体系,同时结合 tcompression.c 中的真实调用,展示其如何作为数据库行级/列级压缩后端之一参与数据落盘。读完本文,你将掌握:如何用conan create生成该库的静态/动态包、如何在项目里通过fast-lzma2::fast-lzma2链接并使用FL2_*系列压缩 API,以及 TDengine 内部调用它的底层路径与编译细节。
一、Fast LZMA2 是什么,为什么 TDengine 需要它
Fast LZMA2 是一个面向高吞吐场景优化的 LZMA2 压缩算法实现,与 xz 工具使用的标准 LZMA/LZMA2 格式兼容,但压缩与解压速度显著更快。TDengine 作为面向工业物联网(IIoT)的时序数据库,存储层需要兼顾压缩率与写入吞吐,因此在多级压缩(Level 1 与 Level 2 两级压缩)设计中,将 fast-lzma2 作为二级压缩的可选压缩器之一,用于对已做一级差分/位压缩的数据流再压一道。
本仓库中该功能的载体是一个完整的 Conan 包:
- 包名:
fast-lzma2,版本1.0.1 - 许可证:BSD-3-Clause 与 GPL-2.0 双许可
- 上游源码:
conor42/fast-lzma2(本仓库通过固定 commit 拉取,见下文)
从源码结构看,仓库采用“双轨依赖”策略:既保留了 cmake/external.cmake 中基于 ExternalProject 的ext_lzma2传统构建路径(Linux 下下载fast-lzma2-ded964d203ca.tar.gz并用 lzma2.Makefile 编译),又提供了 conan/fast-lzma2 这套现代化的 Conan 配方,供启用 Conan 的构建流程使用。
二、Conan 包配方解析
包的核心逻辑位于 conan/fast-lzma2/conanfile.py,下面分段拆解其关键设计。
2.1 包元信息与版本锁定
class FastLzma2Conan(ConanFile): name = "fast-lzma2" version = "1.0.1" license = "BSD-3-Clause" url = "https://github.com/conor42/fast-lzma2" description = "Fast LZMA2 Library - an optimized LZMA2 compression algorithm" topics = ("compression", "lzma2", "fast-lzma2") settings = "os", "compiler", "build_type", "arch" ... # Pin upstream source to a specific commit (avoid relying on local vendored sources) _commit = "ded964d203cabe1a572d2c813c55e8a94b4eda48"配方将上游源码钉死在 commitded964d2...,保证任何环境下产出的二进制一致。注意与 cmake/external.cmake 中ext_lzma2下载的归档(fast-lzma2-ded964d203ca.tar.gz,SHA256 为ee71c637966a7ac429a245e2ee96a7a7ce52eb59087899f07cd1068a41c3af0e)完全一致——两条依赖路径指向同一个源码版本。
2.2 源码获取与镜像回退
def source(self): _filename = f"fast-lzma2-{self._commit[:12]}.tar.gz" _public_url = f"https://github.com/conor42/fast-lzma2/archive/{self._commit}.tar.gz" _mirror = os.environ.get("DEPS_MIRROR_URL", "") if _mirror: try: get(self, f"{_mirror}/{_filename}", strip_root=True) return except Exception: self.output.warning(f"Mirror download failed, falling back to public URL") get(self, _public_url, strip_root=True)source()方法优先从环境变量DEPS_MIRROR_URL指向的镜像站拉取归档(便于离线或受限网络环境),镜像失败则回退到上游 GitHub 归档地址。strip_root=True会去掉归档顶层的fast-lzma2-<commit>/目录。
2.3 构建选项
| 选项 | 默认值 | 说明 |
|---|---|---|
| shared | False | 是否构建为共享库 |
| fPIC | True | 生成位置无关代码(非 Windows 平台) |
配方中的config_options()在 Windows 上删除fPIC;configure()在shared=True时移除fPIC,同时移除 C++ 相关的compiler.libcxx/compiler.cppstd设置,因为这是纯 C 库。
2.4 用 make 而非 CMake 构建
def build(self): cflags = "-Wall -O2 -pthread" asflags = "-Wa,--noexecstack" if self.options.get_safe("fPIC"): cflags += " -fPIC" if self.settings.build_type == "Debug": cflags = cflags.replace("-O2", "-O0 -g") self.run( f'make CFLAGS="{cflags}" ASFLAGS="{asflags}" CC={self.settings.get_safe("compiler", default="gcc")} libfast-lzma2', cwd=source_folder, )该库使用上游自带 Makefile 构建,配方只是透传优化与平台标志:
- Release 默认
-Wall -O2 -pthread(外加非 Windows 下的-fPIC); - Debug 切换为
-O0 -g; - 汇编标志
-Wa,--noexecstack用于防止可执行栈(安全加固)。
这些标志与 cmake/in/lzma2.Makefile 中ext_lzma2的默认值保持一致。该 Makefile 还揭示了平台相关的关键细节:
ifeq ($(x86_64),1) CFLAGS+=-DLZMA2_DEC_OPT OBJ+=lzma_dec_x86_64.o endif即在 x86_64 架构上会自动定义LZMA2_DEC_OPT宏,并额外编译lzma_dec_x86_64.o汇编优化模块,大幅加速解压路径。
2.5 打包与链接信息
package()阶段把 LICENSE/COPYING 拷入licenses/,把fast-lzma2.h、fl2_errors.h拷入include/,并按 shared/static 与平台分别放置库文件:
- Windows 共享库:
bin/*.dll - macOS 共享库:
lib/*.dylib* - Linux 共享库:
lib/*.so* - 静态库:
lib/*.a
package_info()对外暴露的链接信息为:
self.cpp_info.libs = ["fast-lzma2"] if self.settings.os in ["Linux", "FreeBSD"]: self.cpp_info.system_libs.append("pthread") if self.settings.os == "Windows" and self.options.shared: self.cpp_info.defines.append("FL2_DLL_IMPORT=1")即 Linux/FreeBSD 消费方需自动带上pthread;Windows 上以 DLL 方式使用时,通过FL2_DLL_IMPORT=1宏声明导入符号。
三、构建与测试:完整命令行手册
3.1 创建并安装包
在包含conanfile.py的目录(即 conan/fast-lzma2)下执行:
# 本地构建并安装到 Conan 缓存 conan create . --build=missing # 验证安装 conan list "fast-lzma2/*"conan create会依次执行 export → source → build → package → test_package,其中 test_package 的验证是自动触发的。
3.2 不同配置的构建
# 构建共享库版本 conan create . --build=missing -o fast-lzma2/*:shared=True # 构建 Debug 版本 conan create . --build=missing -s build_type=Debug # 关闭 fPIC(例如不需要位置无关代码的场景) conan create . --build=missing -o fast-lzma2/*:fPIC=False # 交叉编译到 ARM64 conan create . --build=missing -s arch=armv8 # 使用 Android 工具链 profile 交叉编译 conan create . --build=missing -pr:h=android-armv83.3 导出与发布到私有仓库
# 导出到本地 Conan 仓库(可指定 user/channel) conan export . fast-lzma2/1.0.1@ conan export . fast-lzma2/1.0.1@mycompany/stable # 添加远程源并上传 conan remote add myremote http://my-conan-server.com conan upload "fast-lzma2/1.0.1" -r myremote --all3.4 包自检(test_package)
conan create会自动运行 conan/fast-lzma2/test_package 下的验证工程:
- conanfile.py 通过
self.requires(self.tested_reference_str)依赖刚构建的包,并使用CMakeDeps + CMakeToolchain生成器; - CMakeLists.txt 执行
find_package(fast-lzma2 REQUIRED CONFIG)并链接fast-lzma2::fast-lzma2; - test_package.c 实际调用
FL2_versionNumber()、FL2_compressBound()、FL2_compress()、FL2_decompress(),校验压缩/解压往返后数据一致,打印Test PASSED才视为通过。
四、在项目中使用:Conan + CMake 集成
4.1 方式一:conanfile.txt
[requires] fast-lzma2/1.0.1 [generators] CMakeDeps CMakeToolchain [options] fast-lzma2/*:shared=False安装依赖:
conan install . --build=missing4.2 方式二:conanfile.py
from conan import ConanFile from conan.tools.cmake import cmake_layout class MyProjectConan(ConanFile): settings = "os", "compiler", "build_type", "arch" generators = "CMakeDeps", "CMakeToolchain" def requirements(self): self.requires("fast-lzma2/1.0.1") def layout(self): cmake_layout(self)4.3 CMakeLists.txt 链接
cmake_minimum_required(VERSION 3.15) project(MyProject C) find_package(fast-lzma2 REQUIRED CONFIG) add_executable(myapp main.c) target_link_libraries(myapp fast-lzma2::fast-lzma2)然后使用 Conan 生成的预设构建:
cmake --preset conan-release # 或 conan-debug cmake --build --preset conan-release4.4 TDengine 中的 CMake 集成实证
TDengine 的 Conan 集成层 cmake/conan.cmake 将 fast-lzma2 列为必选依赖:
find_package(fast-lzma2 REQUIRED)并在 cmake/conan.cmake 中通过宏把该目标链接给各业务模块:
target_link_libraries(${tgt} PUBLIC fast-lzma2::fast-lzma2) target_link_libraries(${tgt} PRIVATE fast-lzma2::fast-lzma2)五、API 实战:单次压缩与流式压缩
Fast LZMA2 的 C API 风格与 zstd 高度相似,核心函数均在fast-lzma2.h中声明。包安装后头文件位于<include>/fast-lzma2.h与<include>/fl2_errors.h。
5.1 单次压缩/解压(one-shot API)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "fast-lzma2.h" int main() { const char* data = "Hello, Fast LZMA2!"; size_t src_size = strlen(data); // 1. 计算压缩所需的最大缓冲区 size_t max_size = FL2_compressBound(src_size); char* compressed = malloc(max_size); // 2. 压缩(压缩等级 1-10,推荐 6) size_t compressed_size = FL2_compress(compressed, max_size, data, src_size, 6); if (FL2_isError(compressed_size)) { fprintf(stderr, "Compression error: %s\n", FL2_getErrorName(compressed_size)); return 1; } // 3. 解压 char* decompressed = malloc(src_size); size_t decompressed_size = FL2_decompress(decompressed, src_size, compressed, compressed_size); if (FL2_isError(decompressed_size)) { fprintf(stderr, "Decompression error: %s\n", FL2_getErrorName(decompressed_size)); return 1; } printf("Success!\n"); free(compressed); free(decompressed); return 0; }5.2 流式压缩(面向大文件/大块数据)
#include <stdio.h> #include <stdlib.h> #include "fast-lzma2.h" int compress_file(const char* input_path, const char* output_path) { FILE* fin = fopen(input_path, "rb"); FILE* fout = fopen(output_path, "wb"); if (!fin || !fout) return -1; FL2_CStream* cstream = FL2_createCStream(); if (!cstream) { fclose(fin); fclose(fout); return -1; } size_t init_result = FL2_initCStream(cstream, 6); if (FL2_isError(init_result)) { FL2_freeCStream(cstream); fclose(fin); fclose(fout); return -1; } size_t const buf_size = 16 * 1024; // 16KB 缓冲 void* in_buffer = malloc(buf_size); void* out_buffer = malloc(buf_size); size_t read_size; while ((read_size = fread(in_buffer, 1, buf_size, fin)) > 0) { FL2_inBuffer input = { in_buffer, read_size, 0 }; while (input.pos < input.size) { FL2_outBuffer output = { out_buffer, buf_size, 0 }; size_t result = FL2_compressStream(cstream, &output, &input); if (FL2_isError(result)) goto cleanup; fwrite(out_buffer, 1, output.pos, fout); } } // 冲刷并结束流 FL2_outBuffer output = { out_buffer, buf_size, 0 }; size_t result = FL2_endStream(cstream, &output); if (!FL2_isError(result)) fwrite(out_buffer, 1, output.pos, fout); cleanup: free(in_buffer); free(out_buffer); FL2_freeCStream(cstream); fclose(fin); fclose(fout); return 0; }5.3 版本信息
unsigned version = FL2_versionNumber(); printf("Fast LZMA2 version: %u.%u.%u\n", version / 10000, (version / 100) % 100, version % 100);六、TDengine 源码中的真实调用链
fast-lzma2 并非“打包完即闲置”,它在 TDengine 的压缩模块中被直接使用。入口位于 source/util/src/tcompression.c 的#include "fast-lzma2.h",核心实现在该文件中的 xz 二级压缩实现:
int32_t l2CompressImpl_xz(const char *const input, const int32_t inputSize, char *const output, int32_t outputSize, const char type, int8_t lvl) { size_t len = FL2_compress(output + 1, outputSize - 1, input, inputSize, lvl); if (len > inputSize) { output[0] = 0; // 压缩反而膨胀时,退化为原样存储 memcpy(output + 1, input, inputSize); return inputSize + 1; } output[0] = 1; // 标志位:后续为压缩数据 return len + 1; } int32_t l2DecompressImpl_xz(const char *const input, const int32_t compressedSize, char *const output, int32_t outputSize, const char type) { if (input[0] == 1) { return FL2_decompress(output, outputSize, input + 1, compressedSize - 1); } else if (input[0] == 0) { memcpy(output, input + 1, compressedSize - 1); // 原样存储分支 return compressedSize - 1; } return TSDB_CODE_THIRDPARTY_ERROR; }这段代码揭示了两点工程细节:
- 膨胀保护:压缩结果比原始数据还大时,会退化为“原样存储”,用首个字节
0/1标记区分压缩块与原始块,避免病态数据导致空间浪费; - 直接透传压缩等级:
lvl参数直接传给FL2_compress,意味着压缩等级由上层配置决定。
此外,tcompression.c 中还存在以FL2_compress(dst, *dstSize, src, srcSize, 9)(等级 9,高压缩率)进行整块压缩、FL2_decompress解压的另一处用法。这印证了 fast-lzma2 在 TDengine 压缩体系(compressL1Dict/compressL2Dict两套分发表之外)作为 xz 类压缩器常驻核心 util 库。
七、编译标志与性能调优
7.1 CFLAGS 含义
| 标志 | 含义 |
|---|---|
-Wall | 开启全部常规警告 |
-O2 | Release 优化等级 2 |
-O0 -g | Debug:不优化 + 调试信息 |
-pthread | 启用多线程支持 |
-fPIC | 位置无关代码(共享库需要) |
-Wa,--noexecstack | 禁止可执行栈 |
7.2 特殊宏
LZMA2_DEC_OPT:在 x86_64 上自动定义,启用汇编优化的解压代码(对应lzma_dec_x86_64.o);FL2_DLL_EXPORT:Windows DLL 导出,由 Makefile 在Windows_NT下自动添加(见 lzma2.Makefile);FL2_DLL_IMPORT:Windows DLL 导入,共享库场景下由package_info()自动注入。
7.3 调优建议
- 压缩等级选择:1-3 级压缩快、压缩率低;4-6 级速度与压缩率均衡(推荐);7-10 级压缩率高、速度慢。TDengine 内部使用等级 6 与等级 9 两个档位,可根据数据形态选择。
- 内存占用:等级越高内存开销越大,流式 API(
FL2_createCStream/FL2_compressStream)可精细控制缓冲。 - 多线程:库内部基于 pthread 自动利用多核,编译期默认开启(
-pthread);Linux 下消费方链接时需显式带上pthread(Conan 配方已自动注入cpp_info.system_libs)。
八、常见问题排查
8.1 编译/打包出错
# 清空缓存重建 conan remove "fast-lzma2/*" -c conan create . --build=missing8.2 链接失败
确认 CMake 中正确使用 Conan 生成的 target:
find_package(fast-lzma2 REQUIRED CONFIG) target_link_libraries(your_target fast-lzma2::fast-lzma2)Linux 下如遇 pthread 相关未定义符号,可显式链接线程库:
find_package(Threads REQUIRED) target_link_libraries(your_target fast-lzma2::fast-lzma2 Threads::Threads)8.3 包未找到
conan list "fast-lzma2/*" # 确认是否已安装未安装则在 conan/fast-lzma2 目录下执行conan create . --build=missing。
九、许可证与注意事项
Fast LZMA2 采用双许可:
- BSD-3-Clause:适用于大多数商业与开源项目;
- GPL-2.0:适用于 GPL 兼容项目。
配方在package()阶段将上游的LICENSE与COPYING一并拷入包的licenses/目录(见 conanfile.py),消费方应保留这两份文件以履行许可义务。TDengine 的 conan/README.md 中对 fast-lzma2 与 cppstub 两个 Conan 包做了整体说明,并指出这套 Conan 集成正是为了替代 cmake/external.cmake 中的ext_fast_lzma2ExternalProject 依赖,换取版本固定、可复现构建与统一的跨平台依赖管理。此外需注意:libfast-lzma2.a与libxxhash.a之间存在符号重叠风险(cmake/external.cmake 中的 TODO 注释),因此 TDengine 在链接顺序上强制先DEP_ext_xxhash再DEP_ext_lzma2,在自行集成这两个库时也应保持相同的链接顺序。
【免费下载链接】TDengineHigh-performance, scalable time-series database designed for Industrial IoT (IIoT) scenarios项目地址: https://gitcode.com/GitHub_Trending/tde/TDengine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考