優雅地實現 TCP 壓縮傳輸
集群式、負載均衡的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格式壓縮。
定義這幾種格式:
typeCompressTypebyte const( // 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定義了壓縮類型。
typeCompressConnstruct{ net.Conn r io.Reader w io.Writer compressType CompressType }
覆蓋 net.Conn 的讀寫方法:
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) }
NewCompressConn 是輔助創建方法:
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 }
對于 zip 格式的寫,寫完后我們需要立即 Flush ,所以也需要對它包裝一下:
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 }
這樣我們就實現了一個可以解壓縮/壓縮的讀寫 net.Conn 對象,你可以通過 NewCompressConn 方法對一個正常的 net.TCPConn 進行包裝,而對它的調用就像一個普通的 net.Conn 一樣。
來自:http://colobu.com/2016/11/04/create-a-compressed-TCP-by-Go/