Gin的gzip中间件nanmu42-gzip
文章目录
背景
最近在做一个 web 版的展示大屏,前端靠 HTTP(S)+JSON 和后端交互,部分图形是密集的地理点位和时间序列,HTTP 返回数据量较大,公网上加载速度不佳。
调研
考虑压缩 HTTP 返回,选了 gzip 这个常规选项。
gzip 在压缩时,得考虑几点:
- 内容类型是否对压缩友好:例如 JPEG 本身已经压缩过,再次压缩收益太小,费力不讨好;
- 内容大小是否值得压缩:太小的内容压缩效果不好(甚至有可能比原文还大,毕竟有字典等开销),另外小内容本身传输时间短,直接放过去还能节约 CPU 和内存。
Nginx
- Nginx 的压缩一般是用 ngx_http_gzip_module;
- 根据 Content-Type 判断内容类型是否对压缩友好,要压缩的类型由用户定义;
- 根据返回的 Content-Length 判断内容大小是否值得压缩;
当返回的 JSON 大于 2KB 时,Golang 会使用 chunk 的形式传输,这个时候没有 Content-Length,ngx_http_gzip_module 不会进行内容压缩。
gin-contrib/gzip
- 根据扩展名判断内容类型是否对压缩友好;
- 根据 Path 判断是否启用压缩;
- 不支持判断内容大小是否值得压缩。
根据 Path 来判断确实可行,但太死板,业务和中间件耦合了。
nanmu42/gzip
- 支持 Gin 和 标准库 net/http ;
- 支持基于 Content-Type、Content-Length、扩展名判断是否压缩;
- 启用压缩的阈值用户可以自定义;
- 压缩级别用户可以自定义;
- 不压缩已经压缩过的返回,不压缩 Head 请求的返回,不影响 HTTP Upgrade ;
- 中间件初始化简单,集成容易;
更进一步
-
还记得返回的 JSON 大于 2KB 时,Golang 会使用 chunk 的形式传输,这个时候没有 Content-Length 带来无法判断内容是否值得压缩的问题吗?
-
我取了个巧,如果 Content-Length 不存在,中间件会去观察 http.ResponseWriter.Write(data []byte) 的第一次调用时的 len(data),如果此时 len(data) 已经大于启用压缩的阈值,那么可以安全地开始压缩。
效率
- 当返回体积不大时,Handler会智能地跳过压缩,这个过程带来的代价可以忽略不记;
- 当返回体积足够大时,Handler会进行gzip压缩,这个过程有着合理的代价。
|
|
局限性
-
你应该总是在返回中提供Content-Type。虽然Handler会在Content-Type缺失时使用http.DetectContentType()进行猜测,但是效果并没有那么好;
-
返回的Content-Length 缺失时,Handler可能会缓冲返回的报文数据以决定报文是否大到值得进行压缩,如果MinContentLength设置得太大,这个过程可能会带来内存压力。Handler针对这个情况做了一些优化,例如查看http.ResponseWriter.Write(data []byte)在首次调用时的 len(data),以及资源复用。
AC 自动机
原本我使用 Strings.Contains() 配合循环来判断文件后缀 /MIME 是否在支持压缩的列表中,但 benchmark 下来效果不太好。做了一些搜索后发现 Cloudflare 实现了一个 AC 自动机来做这个事情。和维护者聊了聊之后,我用了它的一个 fork: https://github.com/signalsciences/ac
Sync.Pool
Sync.Pool 用来做对象重用,以降低系统内存分配和 Go 垃圾回收的压力,一开始我只对 gzip.Writer 做了对象重用,但发现中间件对内存的影响还有一些大,后来我用了第二个 Sync.Pool 重用 wrapper,内存使用量和 CPU 时间都有了可观的改善。
两个调优之后,CPU 时间下降为调优前的 40%,内存使用量下降为原先的一半。
使用示例
默认设置DefaultHandler()可以满足大部分场景。
Gin
|
|
net/http
|
|
定制Handler
使用NewHandler()可以定制参数以满足你的特殊需要:
|
|
RequestFilter 和 ResponseHeaderFilter 是 interface. 你可以实现你自己的过滤器。
源码
handler
|
|
requestfilters
|
|
responsefilters
|
|
writerwrapper
|
|
参考
经历心得分享:用 Web 框架 Gin 开发 API,但业务返回的 JSON 太大,调研了一圈,最后自己写了个中间件做 gzip 压缩,记录下调研和开发调优时的心得
文章作者 Forz
上次更新 2021-04-29