背景 最近在做一个 Web 版的展示大屏,前端靠 HTTP(S)+JSON 和后端交互,部分图形是密集的地理点位和时间序列,HTTP 返回数据量较大,公网上加载速度不佳。 调研 考虑压缩 HTTP 返回,选了 gzip 这个常规选项。 gzip 在压缩时,得考虑几点: 内容类型是否对压缩友好:例如 JPEG 本身已经压 ..

经历心得分享:用 Web 框架 Gin 开发 API,但业务返回的 JSON 太大,调研了一圈,最后自己写了个中间件做 gzip 压缩,记录下调研和开发调优时的心得

背景

最近在做一个 Web 版的展示大屏,前端靠 HTTP(S)+JSON 和后端交互,部分图形是密集的地理点位和时间序列,HTTP 返回数据量较大,公网上加载速度不佳。

调研

考虑压缩 HTTP 返回,选了 gzip 这个常规选项。

gzip 在压缩时,得考虑几点:

先考察了 Nginx

当返回的 JSON 大于 2KB 时,Golang 会使用 chunk 的形式传输,这个时候没有 Content-Lengthngx_http_gzip_module 不会进行内容压缩。

又考察了下 gin-contrib/gzip

根据 Path 来判断确实可行,但太死板,业务和中间件耦合了。

自己造了个轮子

特性

看了gin-contrib/gzipCaddy的实现后,我造了个自己的轮子,欢迎试用、Star 以及反馈:

仓库地址: https://github.com/nanmu42/gzip

文档: https://github.com/nanmu42/gzip/blob/master/README.Chinese.md

更进一步

还记得返回的 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

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%,内存使用量下降为原先的一半。

  • Gin
    7 引用 • 21 回帖
  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    373 引用 • 1237 回帖 • 694 关注
  • middleware
    1 引用
  • Gzip

    gzip (GNU zip)是 GNU 自由软件的文件压缩程序。我们在 Linux 中经常会用到后缀为 .gz 的文件,它们就是 Gzip 格式的。现今已经成为互联网上使用非常普遍的一种数据压缩格式,或者说一种文件格式。

    7 引用 • 11 回帖 • 30 关注
回帖
请输入回帖内容...