跳至内容
grpc 压缩与解压

grpc 压缩与解压

我们知道,http 接口返回的 json 数据是经过压缩的,且使用最多的是 gzip,那么 grpc 呢?
grpc 可以设定压缩和解压的方式,但默认没有压缩。

grpc 压缩与解压

首先需要注册解压和压缩器,这块代码 client 和 server 都需要。

encoding.RegisterCompressor(&compress.GzipCompressor{})

grpc gzip 压缩

这里 encoding 是从 google.golang.org/grpc/encoding 导入的,GzipCompressor 如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package compress

import (
	"compress/gzip"
	"io"
)

// GzipCompressor 实现gRPC Compressor接口
type GzipCompressor struct{}

func (g *GzipCompressor) Compress(w io.Writer) (io.WriteCloser, error) {
	return gzip.NewWriter(w), nil
}

func (g *GzipCompressor) Decompress(r io.Reader) (io.Reader, error) {
	return gzip.NewReader(r)
}

func (g *GzipCompressor) Name() string {
	return "gzip"
}

grpc zstd 压缩

相比 gzip,我更推荐使用 zstd

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package compress

import (
	"io"

	"github.com/klauspost/compress/zstd"
)

// ZstdCompressor 实现gRPC Compressor接口
type ZstdCompressor struct{}

func (z *ZstdCompressor) Compress(w io.Writer) (io.WriteCloser, error) {
	encoder, err := zstd.NewWriter(w)
	if err != nil {
		return nil, err
	}
	return encoder, nil
}

func (z *ZstdCompressor) Decompress(r io.Reader) (io.Reader, error) {
	decoder, err := zstd.NewReader(r)
	if err != nil {
		return nil, err
	}
	return &zstdReadCloser{r: decoder}, nil
}

func (z *ZstdCompressor) Name() string {
	return "zstd"
}

type zstdReadCloser struct {
	r *zstd.Decoder
}

func (z *zstdReadCloser) Read(p []byte) (int, error) {
	return z.r.Read(p)
}

func (z *zstdReadCloser) Close() error {
	z.r.Close()
	return nil
}

grpc snappy 压缩

zstd 相比之下,速度更快,压缩率也更高,如果要更高的压缩率可以考虑 snappy,但 cpu 也更高,不推荐使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package compress

import (
	"io"

	"github.com/golang/snappy"
)

// SnappyCompressor 实现gRPC的Compressor接口
type SnappyCompressor struct{}

// Compress 返回一个io.WriteCloser,用于压缩写入的数据
func (s *SnappyCompressor) Compress(w io.Writer) (io.WriteCloser, error) {
	// Snappy的Writer直接包装目标Writer,无需额外处理
	return snappy.NewBufferedWriter(w), nil
}

// Decompress 返回一个io.ReadCloser,用于解压读取的数据
func (s *SnappyCompressor) Decompress(r io.Reader) (io.Reader, error) {
	// Snappy的Reader实现了io.Reader,直接包装即可
	return &snappyReadCloser{r: snappy.NewReader(r)}, nil
}

func (s *SnappyCompressor) Name() string {
	// Snappy的Reader实现了io.Reader,直接包装即可
	return "snappy"
}

// snappyReadCloser 包装snappy.Reader,实现io.ReadCloser(Snappy的Reader无需关闭)
type snappyReadCloser struct {
	r io.Reader
}

func (s *snappyReadCloser) Read(p []byte) (n int, err error) {
	return s.r.Read(p)
}

func (s *snappyReadCloser) Close() error {
	return nil // Snappy的Reader无需显式关闭
}

grpc client 压缩数据

client 可设定全局的压缩方式,也可以是单次,举个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
	encoding.RegisterCompressor(&compress.GzipCompressor{})
	encoding.RegisterCompressor(&compress.ZstdCompressor{})
	encoding.RegisterCompressor(&compress.SnappyCompressor{})

	conn, err := grpc.NewClient("localhost:18080", 
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		//	设置全局的压缩方式
		grpc.WithDefaultCallOptions(grpc.UseCompressor("zstd")))
	if err != nil {
		panic(err)
	}

	helloClient = hello.NewHelloGreeterClient(conn)

	// 指定单次的压缩方式
	option :=  grpc.UseCompressor("gzip")
	resp, err := helloClient.Greet(context.Background(), &hello.HelloReq{Name: "chun"}, option)
	if err != nil {
		panic(err)
	}
	log.Println(resp.Greet)
}

总结下,client 和 server 都需要注册压缩和解压器,client 还需要设定全局的压缩器或调用时指定压缩器。
相关源码可参考:https://github.com/ijustyce/golang-example/tree/main/grpc

最后更新于