优雅地实现 TCP 压缩传输

zxc321 7年前
   <p>集群式、负载均衡的RPC框架 rpcx 支持多种的序列化库,可以有效的减少消息体的大小,但是对于字符串或者图片的字节slice,明显还可以进一步的压缩,正如fasthttp作者valyala在他的新的开源项目 httpteleport 中描述的: 通过1G的带宽传输10G的数据 (夸张)。</p>    <p>为了在RPC的传输中减少传输的数据大小,我在不影响rpcx整体框架的基础上,参考了httpteleport的实现,对 net.TCPConn 进行了封装,实现了压缩/解压缩功能的 net.Conn ,可以有效的减少带宽,节省公司在带宽上的花费, 以下就是具体的实现。</p>    <p>首先介绍两种压缩格式。</p>    <p>zip是常用的一种压缩格式,Go标准库中提供了它的 实现 。zip原名Deflate,发明者为菲尔·卡茨(Phil Katz),他于1989年1月公布了该格式的资料。ZIP通常使用后缀名“.zip”,它的MIME格式为application/zip。目前,ZIP格式属于几种主流的压缩格式之一。</p>    <p>snappy(以前称Zippy)是Google基于 LZ77 的思路用C++语言编写的快速数据压缩与解压程序库,并在2011年开源。它的目标并非最大压缩率或与其他压缩程序库的兼容性,而是非常高的速度和合理的压缩率。使用一个运行在64位模式下的酷睿i7处理器的单个核心,压缩速度250 MB/s,解压速度500 MB/s。压缩率比gzip低20-100%。Golang也提供了snappy的 实现 。</p>    <p>所以在压缩比和速度的权衡中你可以选择zip格式压缩或者snappy格式压缩。</p>    <p>定义这几种格式:</p>    <pre>  typeCompressTypebyte    const(  // CompressNone represents no compression   CompressNone CompressType = iota  // CompressFlate represents zip   CompressFlate  // CompressSnappy represents snappy   CompressSnappy  )  </pre>    <p>然后定义 CompressConn 类型,它嵌入了一个匿名 net.Conn 类型的字段,作为 net.Conn 的包装,因此它满足 net.Conn 接口。</p>    <p>其中的 r 、 w 可以实现压缩读写,CompressType定义了压缩类型。</p>    <pre>  typeCompressConnstruct{   net.Conn   r io.Reader   w io.Writer   compressType CompressType  }  </pre>    <p>覆盖 net.Conn 的读写方法:</p>    <pre>  func(c *CompressConn) Read(b []byte) (nint, err error) {  returnc.r.Read(b)  }    func(c *CompressConn) Write(b []byte) (nint, err error) {  returnc.w.Write(b)  }  </pre>    <p>NewCompressConn 是辅助创建方法:</p>    <pre>  funcNewCompressConn(conn net.Conn, compressType CompressType) net.Conn {   cc := &CompressConn{Conn: conn}   r := io.Reader(cc.Conn)    switchcompressType {  caseCompressNone:  caseCompressFlate:   r = flate.NewReader(r)  caseCompressSnappy:   r = snappy.NewReader(r)   }   cc.r = r     w := io.Writer(cc.Conn)  switchcompressType {  caseCompressNone:  caseCompressFlate:   zw, err := flate.NewWriter(w, flate.DefaultCompression)  iferr !=nil{  panic(fmt.Sprintf("BUG: flate.NewWriter(%d) returned non-nil err: %s", flate.DefaultCompression, err))   }   w = &writeFlusher{w: zw}  caseCompressSnappy:   w = snappy.NewWriter(w)   }   cc.w = w  returncc  }  </pre>    <p>对于 zip 格式的写,写完后我们需要立即 Flush ,所以也需要对它包装一下:</p>    <pre>  typewriteFlusherstruct{   w *flate.Writer  }    func(wf *writeFlusher) Write(p []byte) (int, error) {   n, err := wf.w.Write(p)  iferr !=nil{  returnn, err   }  iferr := wf.w.Flush(); err !=nil{  return0, err   }  returnn,nil  }  </pre>    <p>这样我们就实现了一个可以解压缩/压缩的读写 net.Conn 对象,你可以通过 NewCompressConn 方法对一个正常的 net.TCPConn 进行包装,而对它的调用就像一个普通的 net.Conn 一样。</p>    <p> </p>    <p>来自:http://colobu.com/2016/11/04/create-a-compressed-TCP-by-Go/</p>    <p> </p>