最近在做一个 web 版的展示大屏,前端靠 HTTP(S)+JSON 和后端交互,部分图形是密集的地理点位和时间序列,HTTP 返回数据量较大,公网上加载速度不佳。
考虑压缩 HTTP 返回,选了 gzip 这个常规选项。
gzip 在压缩时,得考虑几点:
ngx_http_gzip_module
;Content-Type
判断内容类型是否对压缩友好,要压缩的类型由用户定义;Content-Length
判断内容大小是否值得压缩;当返回的 JSON 大于 2KB 时,Golang 会使用 chunk 的形式传输,这个时候没有Content-Length
,ngx_http_gzip_module
不会进行内容压缩。
根据 Path 来判断确实可行,但太死板,业务和中间件耦合了。
看了gin-contrib/gzip和Caddy的实现后,我造了个自己的轮子,欢迎试用、Star 以及反馈:
仓库地址: https://github.com/nanmu42/gzip
文档: https://github.com/nanmu42/gzip/blob/master/README.Chinese.md
Content-Type
、Content-Length
、扩展名判断是否压缩;还记得返回的 JSON 大于 2KB 时,Golang 会使用 chunk 的形式传输,这个时候没有 Content-Length 带来无法判断内容是否值得压缩的问题吗?
我取了个巧,如果 Content-Length 不存在,中间件会去观察http.ResponseWriter.Write(data []byte)
的第一次调用时的len(data)
,如果此时len(data)
已经大于启用压缩的阈值,那么可以安全地开始压缩。
这里分享在造轮子时的两个调优点。
此部分可配合项目各阶段 benchmark 食用: https://github.com/nanmu42/gzip/blob/a0b9dac85d4a0a72f4a2183d3b9bfadf215f2168/docs/benchmarks.md
原本我使用 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%,内存使用量下降为原先的一半。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.