grpc-go源码剖析八十一之如何让grpc框架支持lz4压缩算法呢?

已发表的技术专栏
0  grpc-go、protobuf、multus-cni 技术专栏 总入口

1  grpc-go 源码剖析与实战  文章目录

2  Protobuf介绍与实战 图文专栏  文章目录

3  multus-cni   文章目录(k8s多网络实现方案)

4  grpc、oauth2、openssl、双向认证、单向认证等专栏文章目录)
在这里插入图片描述

本小节中我们分享一下,如何让grpcg-go框架支持lz4压缩算法呢?

1、lz4算法简单介绍?

  • lz4就是一个用16k大小哈希表储存字典并简化检索的LZ77
  • lz4属于无损压缩算法
  • lz4压缩速度非常快

2.如何下载golang版本的lz4压缩算法呢?

go get github.com/pierrec/lz4@v1

3.编写代码,具体实现grpc框架支持lz4压缩算法?

原生grpc框架是不支持lz4压缩算法的,那么如何让grpc框架支持此压缩算法呢?

就是编写代码实现grpc框架压缩数据、解压数据中使用到的几个方法,如Compress、Decompress、DecompressedSize等

既然已经有现成的例子,如gzip了,拿过来修改一下即可。

下面的代码,仅供参考:
lz4.go内容:

1package lz4

2import (

3"io"
4"io/ioutil"
5"sync"
6"github.com/CodisLabs/codis/pkg/utils/log"
7"google.golang.org/grpc/encoding"
8"github.com/pierrec/lz4"
9)

10// Name is the name registered for the lz4 compressor.
11const Name = "lz4"

12func init() {
13.	c := &compressor{}
14.	c.poolCompressor.New = func() interface{} {
15return &writer{Writer: lz4.NewWriter(ioutil.Discard), pool: &c.poolCompressor}
16}
17.	encoding.RegisterCompressor(c)
18}

19type writer struct {
20*lz4.Writer
21.	pool *sync.Pool
22}

23// SetLevel updates the registered gzip compressor to use the compression level specified (gzip.HuffmanOnly is not supported).
24// NOTE: this function must only be called during initialization time (i.e. in an init() function),
25// and is not thread-safe.
26//
27// The error returned will be nil if the specified level is valid.
28//func SetLevel(level int) error {
29//	if level < gzip.DefaultCompression || level > gzip.BestCompression {
30//		return fmt.Errorf("grpc: invalid gzip compression level: %d", level)
31//	}
32//	c := encoding.GetCompressor(Name).(*compressor)
33//	c.poolCompressor.New = func() interface{} {
34//		w, err := lz4.newNewWriterLevel(ioutil.Discard, level)
35//		if err != nil {
36//			panic(err)
37//		}
38//		return &writer{Writer: w, pool: &c.poolCompressor}
39//	}
40//	return nil
41//}

42func (c *compressor) Compress(w io.Writer) (io.WriteCloser, error) {
43.	z := c.poolCompressor.Get().(*writer)
44.	z.Writer.Reset(w)
45return z, nil
46}

47func (z *writer) Close() error {
48defer z.pool.Put(z)
49return z.Writer.Close()
50}

51type reader struct {
52*lz4.Reader
53.	pool *sync.Pool
54}

55func (c *compressor) Decompress(r io.Reader) (io.Reader, error) {
56.	z, inPool := c.poolDecompressor.Get().(*reader)
57if !inPool {
58.		newZ := lz4.NewReader(r)
59return &reader{Reader: newZ, pool: &c.poolDecompressor}, nil
60}

61.	z.Reset(r)
62return z, nil
63}

64func (z *reader) Read(p []byte) (n int, err error) {
65.	n, err = z.Reader.Read(p)
66if err == io.EOF {
67.		z.pool.Put(z)
68}
69return n, err
70}

71func (c *compressor) DecompressedSize(buf []byte) int {
72.	last := len(buf)
73if last < 4 {
74return -1
75}
76return len(buf[9:last-3])
77}

78func (c *compressor) Name() string {
79return Name
80}

81type compressor struct {
82.	poolCompressor   sync.Pool
83.	poolDecompressor sync.Pool
84}

需要将lz4.go文件存放在grpc-go/encoding/lz4目录下;

手动创建lz4目录,然后存方lz4.go文件

主要流程说明:

  • 第71-77行:实现DecompressedSize函数,由前文分析已经知道,此函数,就是返回解压后的数据的大小的。
    • 需要了解一下lz4压缩数据时的协议,需要知道协议标识位占用了几个字节;
    • 只要76行返回值不大于grpc框架允许接收的最大反馈值即可。
  • 第81-84行:使用对象池来创建对象,主要是为了提升效率。

第71-76行,由于时间原因,我并没有深入研究一下lz4的具体协议,这里仅仅是一个参考例子;因此,实际中,需要根据具体的协议,来设置返回值大小;

什么地方涉及到DecompressedSize函数了呢?

如,服务器端接收数据后,需要进行解压缩时的场景;

进入grpc-go/rpc_util.go文件文件中进入recvAndDecompress函数里:
找到下面的代码:

1var size int
2if pf == compressionMade {
3// To match legacy behavior, if the decompressor is set by WithDecompressor or RPCDecompressor,
4// use this decompressor as the default.
5.		d, err = dc.Do(bytes.NewReader(d))
6.		size = len(d)
7} else {
8.		d, size, err = decompress(compressor, d, maxReceiveMessageSize)
9}
10if err != nil {
11return nil, status.Errorf(codes.Internal, "grpc: failed to decompress the received message %v", err)
12}
13} else {

看一下第8行,调用decompress对接收到数据d进行解压,得到解压后的数据d,以及解压后的大小size, 以及解压是否异常err

再看一下第10-12行,如何解压过程中遇到异常,即err不为nil时,就会反馈第11行错误信息。

点击decompress函数,
即进入进入grpc-go/rpc_util.go文件文件中进入decompress函数里:

1func decompress(compressor encoding.Compressor, d []byte, maxReceiveMessageSize int) ([]byte, int, error) {
2.	dcReader, err := compressor.Decompress(bytes.NewReader(d))
3if err != nil {
4return nil, 0, err
5}
6if sizer, ok := compressor.(interface {
7DecompressedSize(compressedBytes []byte) int
8}); ok {
9if size := sizer.DecompressedSize(d); size >= 0 {
10if size > maxReceiveMessageSize {
11return nil, size, nil
12}
13.			buf := bytes.NewBuffer(make([]byte, 0, size+bytes.MinRead))
14.			bytesRead, err := buf.ReadFrom(io.LimitReader(dcReader, int64(maxReceiveMessageSize)+1))
15return buf.Bytes(), int(bytesRead), err
16}
17}
18.	d, err = ioutil.ReadAll(io.LimitReader(dcReader, int64(maxReceiveMessageSize)+1))
19return d, len(d), err
20}

重点看一下第9行,调用DecompressedSize方法,就是调用的是lz4中的DecompressedSize方法,

如果反馈的值大于0,并且大于maxReceiveMessageSize的话,就会执行第11行,反馈异常。

那么,maxReceiveMessageSize值,是如何确定的呢?

如果启动文件里,并没有设置的话,默认值是1024 * 1024 * 4;

在grpc-go/stream.go文件中的newClientStream函数里,找到如下代码:

c.maxSendMessageSize = getMaxSize(mc.MaxReqSize, c.maxSendMessageSize, defaultClientMaxSendMessageSize)
c.maxReceiveMessageSize = getMaxSize(mc.MaxRespSize, c.maxReceiveMessageSize, defaultClientMaxReceiveMessageSize)

defaultClientMaxReceiveMessageSize,就是默认值1024 * 1024 * 4;

当然,前提条件是mc.MaxRespSize, c.maxReceiveMessageSize参数都没有设置。

下一篇文章
  重试机制相关介绍

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码二哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值