0
0

优雅地实现 TCP 压缩传输

鸟窝 发表于 2016年11月15日 13:40 | Hits: 669
Tag: Go

集群式、负载均衡的RPC框架rpcx支持多种的序列化库,可以有效的减少消息体的大小,但是对于字符串或者图片的字节slice,明显还可以进一步的压缩,正如fasthttp作者valyala在他的新的开源项目httpteleport中描述的:通过1G的带宽传输10G的数据(夸张)。

为了在RPC的传输中减少传输的数据大小,我在不影响rpcx整体框架的基础上,参考了httpteleport的实现,对net.TCPConn进行了封装,实现了压缩/解压缩功能的net.Conn,可以有效的减少带宽,节省公司在带宽上的花费, 以下就是具体的实现。

首先介绍两种压缩格式。

zip 是常用的一种压缩格式,Go标准库中提供了它的实现。zip原名Deflate,发明者为菲尔·卡茨(Phil Katz),他于1989年1月公布了该格式的资料。ZIP通常使用后缀名“.zip”,它的MIME格式为application/zip。目前,ZIP格式属于几种主流的压缩格式之一。

snappy (以前称Zippy)是Google基于LZ77的思路用C++语言编写的快速数据压缩与解压程序库,并在2011年开源。它的目标并非最大压缩率或与其他压缩程序库的兼容性,而是非常高的速度和合理的压缩率。使用一个运行在64位模式下的酷睿i7处理器的单个核心,压缩速度250 MB/s,解压速度500 MB/s。压缩率比gzip低20-100%。Golang也提供了snappy的实现

所以在压缩比和速度的权衡中你可以选择zip格式压缩或者snappy格式压缩。

定义这几种格式:

12345678910
type CompressType byteconst (	// CompressNone represents no compression	CompressNone CompressType = iota	// CompressFlate represents zip	CompressFlate	// CompressSnappy represents snappy	CompressSnappy)

然后定义CompressConn类型,它嵌入了一个匿名net.Conn类型的字段,作为net.Conn的包装,因此它满足net.Conn接口。
其中的r、w可以实现压缩读写,CompressType定义了压缩类型。

123456
type CompressConn struct {	net.Conn	r            io.Reader	w            io.Writer	compressType CompressType}

覆盖net.Conn的读写方法:

1234567
func (c *CompressConn) Read(b []byte) (n int, err error) {	return c.r.Read(b)}func (c *CompressConn) Write(b []byte) (n int, err error) {	return c.w.Write(b)}

NewCompressConn是辅助创建方法:

12345678910111213141516171819202122232425262728
func NewCompressConn(conn net.Conn, compressType CompressType) net.Conn {	cc := &CompressConn{Conn: conn}	r := io.Reader(cc.Conn)	switch compressType {	case CompressNone:	case CompressFlate:		r = flate.NewReader(r)	case CompressSnappy:		r = snappy.NewReader(r)	}	cc.r = r	w := io.Writer(cc.Conn)	switch compressType {	case CompressNone:	case CompressFlate:		zw, err := flate.NewWriter(w, flate.DefaultCompression)		if err != nil {			panic(fmt.Sprintf("BUG: flate.NewWriter(%d) returned non-nil err: %s", flate.DefaultCompression, err))		}		w = &writeFlusher{w: zw}	case CompressSnappy:		w = snappy.NewWriter(w)	}	cc.w = w	return cc}

对于zip格式的写,写完后我们需要立即Flush,所以也需要对它包装一下:

1234567891011121314
type writeFlusher struct {	w *flate.Writer}func (wf *writeFlusher) Write(p []byte) (int, error) {	n, err := wf.w.Write(p)	if err != nil {		return n, err	}	if err := wf.w.Flush(); err != nil {		return 0, err	}	return n, nil}

这样我们就实现了一个可以解压缩/压缩的读写net.Conn对象,你可以通过NewCompressConn方法对一个正常的net.TCPConn进行包装,而对它的调用就像一个普通的net.Conn一样。

update : 我增加了 zstd 和 lz4 的支持

原文链接: http://colobu.com/2016/11/04/create-a-compressed-TCP-by-Go/

0     0

评价列表(0)