gzip.writer默认不刷新且非并发安全,网络传输需手动flush;多goroutine写入会panic;gzip.newreader需检查close()错误;压缩级别应依场景权衡性能与带宽。

gzip.Writer 默认不刷新,网络传输时可能卡住
Go 的 gzip.Writer 内部带缓冲,写入数据后不会立刻发到底层 io.Writer(比如 HTTP 响应体或 TCP 连接),而是等缓冲区满或显式调用 Flush()。在网络流式传输场景下,这会导致客户端迟迟收不到首字节,感知为“卡住”或超时。
- HTTP 服务中直接
gzipWriter.Write([]byte{"hello"})后就返回,客户端可能等几秒才收到响应 - 长连接推送日志、监控指标等小包场景,必须手动
Flush()才能确保及时送达 -
gzip.Writer的默认缓冲区大小是 4096 字节,小于该值的数据全被拦在内存里
compress/gzip 不支持并发写入,多 goroutine 写同一个 *gzip.Writer 会 panic
gzip.Writer 不是并发安全的,底层 flate.Writer 状态机不可重入。常见于误用:起多个 goroutine 往同一个 gzip.Writer 写日志、拼接分块数据等。
- 错误现象:
fatal error: concurrent write to gzip writer或随机panic: runtime error: invalid memory address - 正确做法:每个 goroutine 自己 new 一个
gzip.Writer,或者用 channel + 单 goroutine 统一写入 - 如果必须复用压缩器(如避免频繁 malloc),可用
sync.Pool管理*gzip.Writer实例,但每次取用后需Reset()底层io.Writer
gzip.NewReader 读取不完整数据会静默失败,不是所有错误都 panic
用 gzip.NewReader 解压网络流时,如果底层 reader 提前 EOF(比如连接断开、HTTP body 截断),Read() 可能返回 0, nil 或部分数据 + io.ErrUnexpectedEOF,但不会自动校验 gzip 尾部 CRC 和长度 —— 导致解压结果看似成功,实则丢尾。
- 典型场景:HTTP 客户端没设
Timeout,服务端异常关闭连接,客户端拿到截断的 gzip 流 - 必须显式调用
gzReader.Close(),它会检查 trailer 并返回最终错误;只靠Read()返回值不够 - 若底层 reader 是
bytes.Reader或文件,通常没问题;但只要是网络流,务必检查Close()的返回值
gzip.Bits 设置不当会让压缩率和 CPU 开销严重失衡
gzip.NewWriterLevel 第二个参数是压缩级别,但 Go 标准库把 gzip.BestSpeed(1)到 gzip.BestCompression(9)映射到底层 flate 的不同策略。实际影响远不止“快 vs 慢”:级别过高在小数据上反而更慢,级别过低在网络传输中浪费带宽。
立即学习“go语言免费学习笔记(深入)”;
- HTTP API 响应体(几百 ~ 几 KB):用
gzip.BestSpeed(1)或gzip.DefaultCompression(6)最稳;9 级对 2KB 数据压缩耗时可能翻倍,收益不到 5% - 日志归档、离线导出等大文件:可选 9 级,但注意
flate在 9 级会吃光单核,别在高并发服务里无限制用 - 没有
gzip.NoCompression;想禁用压缩,就别套gzip.Writer,直接写原始数据
压缩不是越狠越好,尤其在延迟敏感的网络链路里,CPU 时间和字节节省要算一笔账。很多人调成 9 级就以为“更省流量”,结果 RT 上升、goroutine 积压,反而拖垮整体吞吐。











