go中用compressor接口实现策略模式:定义compress/decompress方法,各算法如gzipcompressor仅持零值字段,内部封装new/write/close;http场景限用gzip/br,内网可选zstd;压缩前需预判数据大小与类型,避免小数据负增益;务必调用close()确保写入完成。

怎么用 interface{} 实现策略模式选压缩算法
Go 里没有抽象类,靠 interface{} 定义统一行为契约。压缩策略的核心就两个方法:Compress() 和 Decompress(),返回 []byte 和 error —— 这样所有算法才能被同一套逻辑调度。
别把策略封装成 struct 带一堆字段再嵌套,容易让调用方误以为要关心内部状态。直接暴露函数变量更轻量:
type Compressor interface {
Compress([]byte) ([]byte, error)
Decompress([]byte) ([]byte, error)
}
常见错误:把 gzip.NewWriter 的 io.WriteCloser 直接当策略返回,结果调用方忘了 Close() 导致数据截断;正确做法是把压缩/解压逻辑全包进方法体内,内部 new、write、close 一气呵成。
- 每个具体实现(如
GzipCompressor)只持有一个零值字段,比如level int,不存*gzip.Writer实例 - 压缩时用
bytes.Buffer接收输出,避免切片复用导致的脏数据 - 解压失败时优先检查输入是否为空或长度为 0,
gzip: invalid header很可能只是传了空字节流
zlib vs gzip vs zstd:选哪个取决于客户端支持和 CPU 预算
zlib 和 gzip 在 Go 标准库都有原生支持,但协议不兼容:zlib 是 RFC 1950,gzip 是 RFC 1952,HTTP Content-Encoding 只认 gzip 和 br,不认 zlib —— 所以服务端若走 HTTP,别用 zlib,除非你控制两端且明确协商了格式。
立即学习“go语言免费学习笔记(深入)”;
zstd 压缩比和速度都优于 gzip,但需要引入 github.com/klauspost/compress/zstd,且老版本 iOS Safari 不支持。实测在中等文本(50KB–500KB)上:zstd.WithEncoderLevel(zstd.SpeedFastest) 比 gzip.BestSpeed 快 2.3 倍,体积小 8%;但启用 zstd.SpeedBestCompression 后 CPU 占用翻倍,而体积只再降 1.2%,不划算。
- HTTP 场景强制用
gzip或br,否则浏览器直接忽略响应头里的Content-Encoding: zstd - 内网 RPC 或私有协议可大胆上
zstd,用zstd.WithDecoderAllowInsecure关闭校验(仅可信环境)提升 5% 解压吞吐 -
snappy压缩率差,但解压极快,适合实时日志传输这种“宁可多发点流量也要低延迟”的场景
如何动态决定是否压缩、用哪一级别
不是所有数据都值得压缩。小于 200 字节的 JSON,gzip 压完反而更大(header 占 18 字节);大于 1MB 的二进制文件(如图片 base64),压缩率常低于 2%,还白耗 CPU。所以策略必须带「预判」逻辑。
推荐在压缩前加一层判断:if len(data) 。其中 <code>isBinaryContentType 查表匹配 image/*、application/pdf、audio/* 等已知难压缩类型。
- 级别选择别硬编码:
gzip.BestCompression在高并发下可能拖垮 CPU,建议按请求 QPS 动态降级 —— QPS > 1000 时自动切到gzip.BestSpeed - 用
http.DetectContentType判断原始内容类型,但注意它只看前 512 字节,对小文件可靠,大文件需结合Content-Type头 - 别在中间件里无差别压缩所有响应,先
if w.Header().Get("Content-Encoding") != ""检查是否已被上游压缩过
gzip.Writer 的 Write 方法不阻塞,但 Close 才真正写入
这是最常踩的坑:gzip.Writer 的 Write() 只是缓冲,实际压缩和写入在 Close() 时才发生。如果只 Write() 就返回,响应体就是空的,或者只有部分数据。
标准库的 gzip.NewWriter 返回的是 io.WriteCloser,但很多人把它当普通 io.Writer 用,漏掉 Close()。更隐蔽的问题是:用 io.MultiWriter 同时写多个目标时,其中一个 Close() 失败会导致整个流程中断。
- 务必用
defer zw.Close(),且确保zw是函数内新创建的实例,别复用 - 如果需要流式压缩并边写边发(如大文件下载),改用
gzip.NewWriterLevel(ioutil.Discard, gzip.BestSpeed)+ 自定义io.Writer实现 flush 逻辑 - 测试时用
httptest.ResponseRecorder拿到原始Body.Bytes(),再用gzip.NewReader解压验证,别只看Content-Length
压缩不是加个 wrapper 就完事,关键在边界控制:什么时候不该压、压到什么程度、谁负责清理资源。这些细节漏一点,线上就可能出现响应乱码、CPU 突增或连接卡死。










