gRPC之数据压缩Snappy、zstd

gRPC之数据压缩Snappy

一、背景

gRPC支持数据压缩,可以有效减少网络传输的数据大小,提高传输效率。

gRPC默认就支持压缩,不需修改代码就可以获得压缩带来的好处。

二、什么是snappy

Snappy是Google开发的一种快速压缩和解压算法。

snappy
美: [ˈsnæpi]
英: ['snæpi]
adj. 精练的;简洁的;漂亮入时的;烦躁的
网络 爽快的;时髦的;漂亮的

Snappy的主要特点包括:

  • 速度快:相比gzip和zlib,Snappy的压缩和解压速度都要快得多。
  • 压缩率低:Snappy的压缩率比gzip低,一般在原始数据的30%~70%左右。
  • 内存占用小: Snappy只需要预留原始数据大小相等的内存就能完成压缩和解压。
  • 简单设计:Snappy算法简单,压缩和解压的代码也比较简短。
  • 广泛支持:目前已经有多种语言实现了Snappy,可以很容易地集成使用。
    Snappy的设计目标更注重速度,针对的是大数据量的网络传输场景。由于压缩速度快,CPU占用少,所以即使压缩率不高,也能提升大数据网络传输的效率。

1. Snappy适合场景

Snappy比较适合以下场景:

  • 大数据网络传输,例如MapReduce、RPC等。
  • 对CPU资源敏感的场景。
  • 内存有限的场景。
  • 压缩和解压需要频繁进行的场景。

Snappy以些许的压缩率作为代价,换来了极快的压缩速度。在对速度和CPU敏感的场景下,Snappy可以作为一个不错的压缩算法选择。

三、demo: Go代码实现了一个snappy压缩格式的压缩器for grpc

开源代码:https://github.com/bytedance/Elkeid/blob/db989a102fafccee9db4eb229af3f73b453022de/server/agent_center/common/snappy/snappy.go

package snappy

// This code is based upon the gzip wrapper in github.com/grpc/grpc-go:
// https://github.com/grpc/grpc-go/blob/master/encoding/gzip/gzip.go

import (
	"io"
	"io/ioutil"
	"sync"

	snappylib "github.com/golang/snappy"
	"google.golang.org/grpc/encoding"
)

const Name = "snappy"

type compressor struct {
    
    
	poolCompressor   sync.Pool
	poolDecompressor sync.Pool
}

type writer struct {
    
    
	*snappylib.Writer
	pool *sync.Pool
}

type reader struct {
    
    
	*snappylib.Reader
	pool *sync.Pool
}

func init() {
    
    
	c := &compressor{
    
    }
	c.poolCompressor.New = func() interface{
    
    } {
    
    
		w := snappylib.NewWriter(ioutil.Discard)
		return &writer{
    
    Writer: w, pool: &c.poolCompressor}
	}
	encoding.RegisterCompressor(c)
}

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

func (c *compressor) Decompress(r io.Reader) (io.Reader, error) {
    
    
	z, inPool := c.poolDecompressor.Get().(*reader)
	if !inPool {
    
    
		newR := snappylib.NewReader(r)
		return &reader{
    
    Reader: newR, pool: &c.poolDecompressor}, nil
	}
	z.Reset(r)
	return z, nil
}

func (c *compressor) Name() string {
    
    
	return Name
}

func (z *writer) Close() error {
    
    
	err := z.Writer.Close()
	z.pool.Put(z)
	return err
}

func (z *reader) Read(p []byte) (n int, err error) {
    
    
	n, err = z.Reader.Read(p)
	if err == io.EOF {
    
    
		z.pool.Put(z)
	}
	return n, err
}

这个代码是基于grpc-go库中的gzip压缩器实现的。grpc-go是Go语言的gRPC库的源码,其中包含了一个gzip压缩格式的实现。这个Snappy的压缩器实现,是参考和借鉴了grpc-go中gzip压缩器的实现方式,使用了类似的结构和方法。

主要的相似点是:

  1. 定义compressor结构体实现压缩接口
  2. 使用sync.Pool重用压缩对象
  3. 实现Compress和Decompress方法
  4. 注册压缩器到gRPC
    所以本质上,是用类似的方式实现了另一种压缩算法Snappy,而不是直接使用grpc-go中的gzip

主要逻辑如下:

  1. 定义了compressor结构体,包含sync.Pool成员用于重用压缩和解压的对象,减少GC。
  2. 定义了writer和reader结构体,封装了snappy的Writer和Reader,并包含一个指向compressor的pool,用于对象重用。
  3. 在init函数中注册了compressor到gRPC的压缩注册表中
  4. Compress方法会从池中取一个writer,reset并返回用于压缩的writer。
  5. Decompress方法会尝试从池中取reader,如果没有则新建,并返回用于解压的reader。
  6. Name方法返回压缩格式名称"snappy"。
  7. writer的Close方法会将writer放回池中重用。
  8. reader的Read方法在读完时会将reader放回池中重用。
    总结:
  9. 使用sync.Pool重用对象减少GC开销。
  10. 封装snappy的Writer/Reader实现gRPC压缩接口。
  11. 注册该compressor以支持snappy格式。
  12. 调用grpc时会自动使用该压缩器进行数据压缩。
    这样实现了一个高效的snappy压缩器来优化gRPC的数据传输。

1. 这段代码怎么保证并发安全的?

snappy在Go语言实现中自己实现了并发安全支持。具体来看:

  1. 定义了compressor结构体,内部维护两个sync.Pool池
  2. 在Compress方法中,从池中获取writer实例,重置并返回,实现复用
  3. 在Decompress中,从池中获取reader实例,或新建实例,并返回
  4. writer和reader在使用完成后会放回池中
    通过这种sync.Pool的机制,snappy实现了并发安全的压缩和解压。

sync.Pool之所以能够实现 goroutine 安全,的确是利用了其内部的互斥锁机制。
具体来看,sync.Pool 内部维护了一个 private pool 字段,它是一个 stack,用于存储缓存的对象。并且 pool 字段用一个 mutex 保护,以实现互斥访问。
当我们调用 Get() 方法取对象时,会对 mutex 加锁,从 pool 栈顶拿对象,解锁后返回。
当调用 Put() 方法置对象时,也是对 mutex 加锁,将对象推入 pool 栈顶,解锁。
正是通过这种互斥锁的机制,sync.Pool 实现了对 pool 的并发安全访问。多个 goroutine 调用 Get() 和 Put() 时,能够同步执行,避免了竞争条件。

四、什么是zstd

zstd是一种广泛使用的开源数据压缩算法。
它的全称是Zstandard,主要有以下几个特点:

  1. 压缩比
    zstd的压缩比非常高,通常可以将数据压缩到原始大小的20%左右,压缩效果明显优于一些老的压缩算法。
  2. 压缩速度
    相比其他高压缩比的算法,zstd的压缩速度也较快,仅次于速度最快的算法如LZ4。在多核CPU上,支持并行压缩来进一步提升速度。
  3. 内存占用
    zstd的编码和解码并不占用很多内存,可以在内存受限的环境下使用。
  4. 可扩展性
    zstd支持从1至22的压缩等级,压缩比和速度可根据不同等级进行平衡。
  5. 错误检测
    zstd在数据块添加校验值,可以检测压缩数据是否损坏。
  6. 多语言支持
    zstd已经支持了C、C++、Java、Go、Python、JavaScript等多种语言。
    zstd通过其高压缩比和快速的压缩速度,已经成为各种应用中的流行选择,被广泛应用于文件的压缩、数据存储、网络数据传输等方面。
    例如Linux kernel、Docker、Kafka、Hadoop、gRPC等知名开源项目都采用了zstd进行数据压缩。它可以明显减小数据量,提高存储和传输效率。

五、 zstd和snappy有什么区别,如何选择?

zstd和snappy都是流行的压缩算法,作为gRPC压缩器的区别主要有:

  1. 压缩比
    zstd的压缩比明显高于snappy,通常能够压缩到原始数据的20%左右,而snappy压缩比較低,大约在50%-70%之间。
    如果对网络带宽要求高,zstd可以提供更高的压缩效率。

  2. 压缩速度
    snappy的压缩速度非常快,是目前最快的压缩算法之一。zstd相比而言压缩速度较慢。
    如果对压缩加速要求高,snappy更合适。

  3. 内存占用
    snappy的编码解码占用内存很小,非常适合在内存受限的环境使用。
    zstd编码解码时需要更多内存。

  4. 兼容性
    snappy兼容性好,各语言的实现也比较成熟。zstd更新,语言支持还在持续改进中。
    所以其实两者有各自的优点,没有绝对的好坏。需要根据具体场景和需求选择。

如果需要高压缩率,zstd更合适;如果需要高速或内存受限则snappy更好。

六、demo: Go代码实现了一个zstd压缩格式的压缩器for grpc

开源代码:https://github.com/bytedance/Elkeid/blob/db989a102fafccee9db4eb229af3f73b453022de/server/agent_center/common/zstd/zstd.go

package zstd

import (
	"google.golang.org/grpc/encoding"
	"io"

	"github.com/DataDog/zstd"
)

const Name = "zstd"

func init() {
    
    
	c := &compressor{
    
    }
	encoding.RegisterCompressor(c)
}

func (c *compressor) Compress(w io.Writer) (io.WriteCloser, error) {
    
    
	z := zstd.NewWriter(w)
	return z, nil
}

func (c *compressor) Decompress(r io.Reader) (io.Reader, error) {
    
    
	z := zstd.NewReader(r)
	return z, nil
}

func (c *compressor) Name() string {
    
    
	return Name
}

type compressor struct {
    
    
}

实现了一个zstd压缩格式的gRPC压缩器。
分为以下几步:

  1. 导入需要的包,包括gRPC编解码包和zstd压缩算法包。
  2. 定义压缩器名称const Name = “zstd”。
  3. 初始化函数init()注册压缩器。
  • 创建compressor对象
  • 通过encoding.RegisterCompressor注册压缩器
  1. 实现Compress和Decompress方法。
  • Compress使用zstd.NewWriter封装writer生成zstd压缩writer
  • Decompress使用zstd.NewReader封装reader生成zstd解压reader
  1. 实现Name()方法,返回压缩器名称"zstd"。
  2. 定义compressor结构体实现gRPC的Compressor接口。

这样就实现了一个zstd格式的gRPC压缩器。

可以通过gRPC选项grpc.UseCompressor(Name)指定使用该压缩器。

压缩器会在gRPC消息编码时压缩数据,在解码时解压数据,从而减小消息大小,提高传输效率。
这是gRPC压缩器的典型实现方式,通过grpc.RegisterCompressor注册不同的 compress/decompress 实现来添加自定义压缩格式。

猜你喜欢

转载自blog.csdn.net/inthat/article/details/132635927