最近在做一个 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%,内存使用量下降为原先的一半。
1
lhx2008 2019-12-13 16:36:29 +08:00 via Android
emmm,这种轮子也要搞的吗,nginx 和 go 两边都没法改?
|
2
nanmu42 OP @lhx2008 感谢回复。
原因调研那段也提到了,返回的 JSON 大于 2KB 时,Golang 会使用 chunk 的形式传输,这个时候没有 Content-Length,Nginx 这边判断不了是否压缩。 其实还有个原因,我们的应用部署在云服务的 k8s 集群里,网络本身由云服务的 ingress 提供,不太想再加一层 Nginx. |
3
monsterxx03 2019-12-13 17:02:54 +08:00 2
端 client 显示发送 Accept-Encoding: deflate, gzip header 的话, nginx 是能 gzip chunk response 的.
你可以用 curl --compressed http://xxx 然后 tcpdump 抓包看一下, 结果会被压缩的. 用 k8s 的话, 加个 nginx 做 sidecar container 也不麻烦, 虽然我能理解你不想加的原因... |
4
janxin 2019-12-13 17:30:02 +08:00
咦,这个问题还真没注意过,毕竟目前 JSON 都不大
|
5
Ehco1996 2019-12-13 18:28:07 +08:00
请问一下云服务提供的默认的 ingress 是啥?阿里云貌似用的还是 nginx,难道不能简单的配置一下 ingress 来实现压缩的目的么?
|
6
optional 2019-12-13 19:21:40 +08:00 via iPhone
nginx 可以的。。。
|
7
bolide2005 2019-12-13 19:59:10 +08:00
我们的服务( API Gateway )用 golang 返回过包括语音在内的大文件,并没有遇到需要自己压缩的情况,感觉有点问题……
|
8
xiangyuecn 2019-12-13 20:54:39 +08:00
不知道文中 ResponseWriter 这玩意有没有 buffer、flush 的概念,也许哪里调大点屁事都没有了😂
|
9
diveIntoWork 2019-12-13 23:55:54 +08:00 via Android
前端怎么解压缩呢。。
|
10
securityCoding 2019-12-14 10:31:26 +08:00
@diveIntoWork 浏览器引擎干的事 ,前端并不需要关注 , 压缩传输在 http 协议标准中有定义
|
11
richzhu 2019-12-14 11:28:27 +08:00
目前在开发一些东西,想请教一下,如果每个 json 大概在 20 - 50k 之间,内网环境 有必要使用 gzip 吗? 或者说 使用 gzip 优化效果明显吗?
|
12
ihciah 2019-12-14 12:20:59 +08:00
待匹配字符串集完全固定并且很小,感觉用 Map 就可以了
|
13
nanmu42 OP |
14
nanmu42 OP @ihciah 是的,完全匹配 map 是个好办法。
MIME 不是完全匹配(可能会在末尾多一个; charset: utf8 ),所以没用上。 benchmark 下来,这个用例下 AC 自动机比 String.Contains 还要快。 |
15
nanmu42 OP @xiangyuecn 有 2K 的 buffer,私有不可调。
我觉得压缩这事在 net/http 里解决了的话真的挺好的,当然用中间件也不是不行。 |
16
nanmu42 OP @diveIntoWork 对前端透明,浏览器解决了,不用关注的。
|
17
ccpp132 2019-12-14 13:23:45 +08:00 via Android
你的需求把 mime 切一下再用 map 就行了。多模匹配还没必要
|
19
xcstream 2019-12-15 03:40:54 +08:00
我觉得研究压缩优化空间不大 加带宽或者用前端缓存更有用
|