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

2019-12-13 16:33:23 +08:00
 nanmu42

背景

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

5845 次点击
所在节点    Go 编程语言
19 条回复
lhx2008
2019-12-13 16:36:29 +08:00
emmm,这种轮子也要搞的吗,nginx 和 go 两边都没法改?
nanmu42
2019-12-13 16:41:53 +08:00
@lhx2008 感谢回复。

原因调研那段也提到了,返回的 JSON 大于 2KB 时,Golang 会使用 chunk 的形式传输,这个时候没有 Content-Length,Nginx 这边判断不了是否压缩。

其实还有个原因,我们的应用部署在云服务的 k8s 集群里,网络本身由云服务的 ingress 提供,不太想再加一层 Nginx.
monsterxx03
2019-12-13 17:02:54 +08:00
端 client 显示发送 Accept-Encoding: deflate, gzip   header 的话, nginx 是能 gzip chunk response 的.

你可以用 curl --compressed http://xxx 然后 tcpdump 抓包看一下, 结果会被压缩的.

用 k8s 的话, 加个 nginx 做 sidecar container 也不麻烦, 虽然我能理解你不想加的原因...
janxin
2019-12-13 17:30:02 +08:00
咦,这个问题还真没注意过,毕竟目前 JSON 都不大
Ehco1996
2019-12-13 18:28:07 +08:00
请问一下云服务提供的默认的 ingress 是啥?阿里云貌似用的还是 nginx,难道不能简单的配置一下 ingress 来实现压缩的目的么?
optional
2019-12-13 19:21:40 +08:00
nginx 可以的。。。
bolide2005
2019-12-13 19:59:10 +08:00
我们的服务( API Gateway )用 golang 返回过包括语音在内的大文件,并没有遇到需要自己压缩的情况,感觉有点问题……
xiangyuecn
2019-12-13 20:54:39 +08:00
不知道文中 ResponseWriter 这玩意有没有 buffer、flush 的概念,也许哪里调大点屁事都没有了😂
diveIntoWork
2019-12-13 23:55:54 +08:00
前端怎么解压缩呢。。
securityCoding
2019-12-14 10:31:26 +08:00
@diveIntoWork 浏览器引擎干的事 ,前端并不需要关注 , 压缩传输在 http 协议标准中有定义
richzhu
2019-12-14 11:28:27 +08:00
目前在开发一些东西,想请教一下,如果每个 json 大概在 20 - 50k 之间,内网环境 有必要使用 gzip 吗? 或者说 使用 gzip 优化效果明显吗?
ihciah
2019-12-14 12:20:59 +08:00
待匹配字符串集完全固定并且很小,感觉用 Map 就可以了
nanmu42
2019-12-14 12:35:34 +08:00
@richzhu
我们的是上 M 了(大屏显示用的时序数据),公网传输。

内网环境 5M 内应该都可以接受的。
nanmu42
2019-12-14 12:38:58 +08:00
@ihciah 是的,完全匹配 map 是个好办法。
MIME 不是完全匹配(可能会在末尾多一个; charset: utf8 ),所以没用上。
benchmark 下来,这个用例下 AC 自动机比 String.Contains 还要快。
nanmu42
2019-12-14 12:40:59 +08:00
@xiangyuecn 有 2K 的 buffer,私有不可调。
我觉得压缩这事在 net/http 里解决了的话真的挺好的,当然用中间件也不是不行。
nanmu42
2019-12-14 12:41:37 +08:00
@diveIntoWork 对前端透明,浏览器解决了,不用关注的。
ccpp132
2019-12-14 13:23:45 +08:00
你的需求把 mime 切一下再用 map 就行了。多模匹配还没必要
nanmu42
2019-12-14 15:30:22 +08:00
@ccpp132 AC 自动机确实快得飞起,也用不着自己去切,方便。
xcstream
2019-12-15 03:40:54 +08:00
我觉得研究压缩优化空间不大 加带宽或者用前端缓存更有用

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/628791

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX