LZ4解压比gzip快是因为其核心为查表+memcpy,无需Huffman解码或滑动窗口状态维护,单核可达GB/s;但正因无数据完整性校验,帧头长度不符或字节缺失即直接返回io.ErrUnexpectedEOF,而gzip会报校验和错误。
别用 compress/gzip 或 compress/zlib 解大日志——LZ4 解压速度是它们的 3 倍以上,但必须按帧对齐、显式处理长度头,否则一读就 io.ErrUnexpectedEOF。
LZ4 的解压核心是查表+memcpy,不依赖 Huffman 解码或滑动窗口状态维护,所以单核就能跑满 GB/s。但这也意味着它完全不校验数据完整性——只要输入字节流缺一个字节、或帧头长度声明和实际不符,lz4.NewReader 就会直接返回 io.ErrUnexpectedEOF,而不是像 gzip 那样报 zlib: invalid checksum。
常见错误现象:
binary.Read(r, binary.LittleEndian, &n) 后没检查 n 是否超出剩余缓冲区io.Copy 直接解压到 bytes.Buffer,但源 io.Reader 实际只提供部分帧,导致解压中途卡住标准库没有 LZ4 支持,必须用 github.com/pierrec/lz4。它提供 lz4.NewReader,但注意:它默认期望输入是完整 LZ4 帧(含魔数 0x184D2204),而很多日志系统(如 Filebeat + Logstash pipeline)发的是“裸 LZ4 数据”——即只有压缩体,没帧头。
立即学习“go语言免费学习笔记(深入)”;
实操要点:
lz4.NewReader(r, lz4.WithZeroFrame())
io.LimitReader(r, int64(length)),不能靠 lz4.NewReader 自己判断边界lz4.Reader 实例——它的内部状态(如字典、滑动窗口)不重置err 可能是 io.ErrUnexpectedEOF 或 lz4.ErrInvalid,二者需区别对待:前者大概率是长度头不准,后者才是数据损坏解压本身是 CPU 密集型,但 LZ4 的 Go 实现(pierrec/lz4)已做 asm 优化,单 goroutine 就能打满一个核。盲目开 100 个 goroutine 解压,并不会提速,反而因调度和内存分配拖慢整体吞吐。
真正该并行的是 I/O 和解压后的文本处理(如 JSON 解析、字段提取):
runtime.NumCPU())从 channel 拿待解压的 []byte(已含长度头)io.LimitReader → lz4.NewReader → io.ReadAll
string(data) —— 日志文本可能含非法 UTF-8,应先用 bytes.Equal 或 utf8.Valid 判断再转不是 LZ4 算法问题,而是协议层没对齐:
lz4.CompressBlock(块模式),接收方却用 lz4.NewReader(帧模式)——块模式无长度头,必须提前知道原始长度binary.Read 读出的 n 实际是第二个块的长度,后续解压必然失败真正难的不是调 API,而是确认你手上的日志字节流,到底是“裸块”、“带帧头”还是“长度头+裸块”——这三种模式的解压入口函数、错误处理逻辑、buffer 分配策略全都不一样。