前一阵子我在调一套日志采集链路,发现问题特别典型:数据攒成一个大文件再做压缩,压缩比漂漂亮亮,但只要改成“边采集边压缩、实时传走”,压缩比噌地一下崩到几乎不压缩。很多人第一反应是“库选错了”“压缩级别没调好”,但我在这个方向踩过几轮之后可以明确告诉你,问题往往出在你把实时数据压缩库理解成了普通压缩库——以为选个LZ4或者Zstandard就能直接用,实际完全不是这么回事。
“实时数据压缩库”这个词,严格说并不是指某一个具体软件,而是一类满足“数据产生后立刻压缩、立刻传输/落盘”场景的算法和库的组合。它适合谁用?做物联网采集端、日志Agent、消息队列传输、监控指标收集、时序数据存储的人,几乎都会碰到。它解决的核心矛盾很简单:数据一刻不停地在产生,你既不想让它占着带宽或者磁盘,又不能等到攒完再处理,因为实时性要求不允许。这篇文章我就想把实时压缩这条链路从原理到选型、再到实际落地和排错,完整拆开来讲一遍。
1. 实时压缩真正难在哪儿:不是算法快慢,而是压缩窗口被切碎了
1.1 通用压缩算法依赖“历史数据”,实时流却没有完整历史
绝大多数压缩算法,比如Deflate、LZ4、Zstandard,底层思路都绕不开 LZ77 系列的滑动窗口:压缩器维护一个已经处理过的历史缓冲区,遇到新数据时,如果在历史窗口里找到重复片段,就用“位置 + 长度”这样的引用替代原始字节。历史数据越丰富,找到重复的概率越高,压缩率自然就越好。
离线压缩一整批文件时,压缩器能看到全部输入,滑动窗口可以从文件头一直积累到文件尾。但实时场景不一样,数据是源源不断到达的,你不可能等全部数据到齐再压缩,所以被迫把数据切成一个又一个块,每块单独压缩。每切一刀,就等于告诉压缩算法:前面那些历史你用不上了,从头开始吧。
这个现象在日志场景里特别明显。比如一行JSON日志两三KB,单独压缩这一行时,压缩器几乎没有历史可参考,只能靠行内重复和固定字段去压,压缩率低得可怜。而把几百行聚合成一个64KB的块再压,日志里常见的重复时间前缀、固定字段名、相同告警模板就能被有效利用,压缩率可能翻好几倍。
我见过不少人做实时压缩时,拿单条消息去调库,测出来效率很差,就反过来说“这个库不行”。其实不是库不行,是压缩粒度选错了。
1.2 软实时与硬实时:先想清楚你的延迟预算
可别一听到“实时”就把指标定成“每条消息立刻压完立刻发”。现实中实时压缩系统绝大多数是软实时——允许数据在内存或缓冲区里停留数十毫秒、数秒,只要端到端延迟在可接受范围内就行。
真正要区分的是两个问题:
- 端到端延迟预算:从数据被采集到压缩数据到达下游,允许多久?如果是100ms以内,那你可能最多攒32KB甚至16KB就得压缩一次。
- 单次压缩耗时上限:压缩必须在某个时间内完成,不能阻塞采集线程。如果采集线程是单线程且不允许阻塞过久,那么高压缩级别基本不用考虑。
这里有个直接反直觉的点:分块越小,延迟越低,但压缩率越差;分块越大,压缩率越好,但意味着你要攒更多数据,延迟和内存占用都更高。实时压缩库的所有设计,本质上都是在“块大小”和“延迟预算”之间找平衡。
一个合理的起步策略通常是:按时间或按字节双重触发,比如“每攒满64KB压缩一次,或者超过2秒还没有攒满64KB,就把当前数据强制flush出去”
