工作中用 GO: web 性能优化之 gzip

写在前面

最近遇到一个业务场景, 用户在第一次使用时会上传大量数据, 未优化前历史2年的数据需要20分钟, 和FE一起沟通后, 选择使用gzip压缩优化, 将时间压缩到5分钟内, 实测下来gzip能达到 3-10倍的压缩率

Go中使用gzip

以 hertz 框架为例, 使用 cwgo 脚手架初始化项目, 默认开启 gzip 中间件

func registerMiddleware(h *server.Hertz) {
    // gzip
    if conf.GetConf().Hertz.EnableGzip {
        h.Use(gzip.Gzip(gzip.DefaultCompression))
    }
}

社区提供的使用示例: gzip/gzip_test.go at main · hertz-contrib/gzip

func TestDecompressGzip(t *testing.T) {
    buf := &bytes.Buffer{}
    gz := compress.AcquireStacklessGzipWriter(buf, gzip.DefaultCompression)
    if _, err := gz.Write([]byte(testResponse)); err != nil {
        gz.Close()
        t.Fatal(err)
    }
    gz.Close()
    router := route.NewEngine(config.NewOptions([]config.Option{}))
    router.Use(Gzip(DefaultCompression, WithDecompressFn(DefaultDecompressHandle)))
    router.POST("/", func(ctx context.Context, c *app.RequestContext) {
        if v := c.Request.Header.Get("Content-Encoding"); v != "" {
            t.Errorf("unexpected `Content-Encoding`: %s header", v)
        }
        if v := c.Request.Header.Get("Content-Length"); v != "" {
            t.Errorf("unexpected `Content-Length`: %s header", v)
        }
        data := c.GetRawData()
        c.Data(200, "text/plain", data)
    })
    request := ut.PerformRequest(router, consts.MethodPost, "/", &ut.Body{Body: buf, Len: buf.Len()}, ut.Header{
        Key: "Content-Encoding", Value: "gzip",
    })
    w := request.Result()
    assert.Equal(t, http.StatusOK, w.StatusCode())
    assert.Equal(t, "", w.Header.Get("Content-Encoding"))
    assert.Equal(t, "", w.Header.Get("Vary"))
    assert.Equal(t, testResponse, string(w.Body()))
    assert.Equal(t, "18", w.Header.Get("Content-Length"))
}

最佳实践

  1. FE上传数据: json = jsonDecode(utf8.decode(GZipCodec().decode(params)));
  2. BE接收时是2 进制数据, 使用 debug + base64Encode 保存了一份, 方便后续调试
  3. 使用上面的 DefaultDecompressHandle, 发现获取的数据和步骤 2 中一样
  4. 和FE联调, 获取到了所有中间步骤的数据
flutter: postUseGzip params:
{metaInfo: {timezone: Asia/Shanghai}...}

flutter: postUseGzip jsonData:
{"metaInfo":{"timezone":"Asia/Shanghai"}...}

flutter: postUseGzip utf8List: [123, 34, 109, 101, 116, ...]

flutter: postUseGzip gzip: [31, 139, 8, 0, 0, 0, 0, 0, 0, ...]
  1. Go 中对 gzip 数据进行处理
func DataGzip(ctx context.Context, c *app.RequestContext) {
    b := c.Request.Body()
    g, err := gzip.NewReader(bytes.NewBuffer(b))
    if err != nil {
        hutil.RespErr(c, err)
        return
    }
    data, err := io.ReadAll(g) // 获取原始 json 数据
    if err != nil {
        hutil.RespErr(c, err)
        return
    }
    hlog.CtxInfof(ctx, fmt.Sprintf("gzip data: %s", string(data)))

        // do something...
    hutil.RespData(c, res)
}
  1. 和 FE 联调, FE 反馈请求无返回, 实测发现是步骤 2 中的 DefaultDecompressHandle 引起的, 会导致 response 使用 gzip 压缩后返回, 导致 FE 请求库无法解析导致的

写在最后

  • 了解web gzip 的原理和框架处理的机制, 可以少走很多弯路
  • debug 过程中保留所有中间步骤并使用 testcase 进行验证, 其实快很多
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容