已发表的技术专栏
0 grpc-go、protobuf、multus-cni 技术专栏 总入口
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内容:
1.package lz4
2.import (
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.
11.const Name = "lz4"
12.func init() {
13. c := &compressor{}
14. c.poolCompressor.New = func() interface{} {
15. return &writer{Writer: lz4.NewWriter(ioutil.Discard), pool: &c.poolCompressor}
16. }
17. encoding.RegisterCompressor(c)
18.}
19.type 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.//}
42.func (c *compressor) Compress(w io.Writer) (io.WriteCloser, error) {
43. z := c.poolCompressor.Get().(*writer)
44. z.Writer.Reset(w)
45. return z, nil
46.}
47.func (z *writer) Close() error {
48. defer z.pool.Put(z)
49. return z.Writer.Close()
50.}
51.type reader struct {
52. *lz4.Reader
53. pool *sync.Pool
54.}
55.func (c *compressor) Decompress(r io.Reader) (io.Reader, error) {
56. z, inPool := c.poolDecompressor.Get().(*reader)
57. if !inPool {
58. newZ := lz4.NewReader(r)
59. return &reader{Reader: newZ, pool: &c.poolDecompressor}, nil
60. }
61. z.Reset(r)
62. return z, nil
63.}
64.func (z *reader) Read(p []byte) (n int, err error) {
65. n, err = z.Reader.Read(p)
66. if err == io.EOF {
67. z.pool.Put(z)
68. }
69. return n, err
70.}
71.func (c *compressor) DecompressedSize(buf []byte) int {
72. last := len(buf)
73. if last < 4 {
74. return -1
75. }
76. return len(buf[9:last-3])
77.}
78.func (c *compressor) Name() string {
79. return Name
80.}
81.type 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函数里:
找到下面的代码:
1.var size int
2.if 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. }
10. if err != nil {
11. return 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函数里:
1.func decompress(compressor encoding.Compressor, d []byte, maxReceiveMessageSize int) ([]byte, int, error) {
2. dcReader, err := compressor.Decompress(bytes.NewReader(d))
3. if err != nil {
4. return nil, 0, err
5. }
6. if sizer, ok := compressor.(interface {
7. DecompressedSize(compressedBytes []byte) int
8. }); ok {
9. if size := sizer.DecompressedSize(d); size >= 0 {
10. if size > maxReceiveMessageSize {
11. return 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))
15. return buf.Bytes(), int(bytesRead), err
16. }
17. }
18. d, err = ioutil.ReadAll(io.LimitReader(dcReader, int64(maxReceiveMessageSize)+1))
19. return 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参数都没有设置。
下一篇文章
重试机制相关介绍