
压缩前先做日志分片,别直接喂给 zlib
直接对整条日志或大块内存调用 compress 或 deflate 效果差——压缩率低、锁争用高、内存抖动明显。真实高吞吐场景下,日志是持续写入的流,必须切片后异步处理。
- 按时间窗口(如 10s)或大小阈值(如 4MB)切片,每个分片生成独立
log_segment.bin文件 - 切片后立刻移交到专用线程池,主线程不等压缩完成,只负责写入未压缩的环形缓冲区
- 避免在压缩线程里做格式解析(如 JSON 提取字段),这部分必须前置到采集线程完成
- zlib 默认的
Z_DEFAULT_COMPRESSION在日志场景偏重,实测Z_BEST_SPEED+ 分片后整体吞吐提升 2.3 倍,压缩率仅降 8% 左右
用 LZ4 替代 zlib,但得关掉 auto flush
LZ4 的 LZ4_compress_default 看似快,但默认行为会在每次调用时 flush 内部状态,导致小分片(
- 改用
LZ4_compress_fast并显式传入加速参数(如acceleration=4),比默认快 1.7x - 禁用所有自动 flush:确保输入 buffer 是完整分片,不拼接、不分段传入
- 启用
LZ4F_createCompressionContext复用上下文,避免每次压缩都 malloc/free 内部哈希表 - 注意 LZ4 不保证跨平台解压兼容性——同一版本号下,x86_64 和 aarch64 的压缩输出可能不一致,生产环境必须锁定 ABI 和编译器版本
压缩后立即 mmap 写盘,别用 std::ofstream
用 std::ofstream 写压缩后的二进制数据,会触发多次小 write() 系统调用 + libc 缓冲管理,I/O 路径长、延迟不可控。尤其在 NVMe 盘上,反而压不住带宽。
- 压缩完成立刻调用
mmap映射目标文件(MAP_SHARED | MAP_POPULATE),然后 memcpy 到映射地址 - 写完调用
msync(非fsync),强制刷脏页到 page cache,由内核后台线程落盘,主线程零等待 - 文件需提前
fallocate预分配空间,否则 mmap 可能触发 on-demand page fault,卡住压缩线程 - 注意
mmap大小不能超过RLIMIT_AS,建议单个 segment ≤ 16MB,超限改用writev+ iovec 分段写
索引与压缩分离,别把 offset 表塞进压缩流
有人把日志时间戳、原始长度、压缩后 offset 打包进压缩数据头部,看似省事,实际破坏了压缩率和随机读能力——这些元数据几乎不重复,硬塞进去反而拉低整体压缩比 12%+,且解压时必须全量读才能定位。
立即学习“C++免费学习笔记(深入)”;
- 元数据单独存为
segment.idx,明文 JSON 或 flatbuffer 格式,不压缩 - 压缩数据文件(
segment.dat)保持纯二进制流,支持mmap + offset随机解压某条日志 - idx 文件本身用
posix_fadvise(..., POSIX_FADV_DONTNEED)提示内核不要缓存,它只被索引线程顺序读 - 删除旧日志时,必须原子地 unlink
.dat和.idx,用rename+unlink组合,避免残留不匹配文件
真正难的不是选哪个压缩库,而是让压缩动作不拖慢采集、不挤占 I/O、不解耦元数据——这三处一松动,存储空间省下的字节,全被调度延迟和内存碎片吃掉了。










