前言:
在使用golang sync.pool的时候遇到一个小坑,这个小坑会导致我们grpc网关的反序列化失败。是什么原因导致的? 😅
该文章后续仍在不断的更新修改中, 请移步到原文地址 http://xiaorui.cc/?p=5969
问题:
简单说,没有清理以前的数据引起的。以往对于socket server和grpc server的拆包解包是用 bytes.Buffer,在每次使用完sync.pool put的时候,会及时的该对象进行reset重置。但这次用了[]byte,需要手动实现[]byte的length重置。
[]byte是个字节切片,他对应的sliceHeader底层是有三个字段,len、cap、data 。 data 是一个数组,cap是数组的大小,len是实际存储数据的大小。那么重置其实操作 len = 0就可以了。但因为len字段是小写不可见,上层无法进行操作。
// xiaorui.cc type SliceHeader struct { Data uintptr Len int Cap int }
看了下bytes.Buffer的reset重置代码,他是通过 obj[:0] 引用的方法来重置len大小的。
下面是我这边修改后的sync.pool []byte应用代码,大家可以参考下。
// xiaorui.cc
package main
import (
"sync"
)
var (
maxSize = 20000 // 20k
defaultBufferPool = newBufferPool(maxSize)
)
type bufferPool struct {
bp *sync.Pool
maxSize int
}
func newBufferPool(maxSize int) *bufferPool {
return &bufferPool{
bp: &sync.Pool{
New: func() interface{} {
b := make([]byte, 2048)
return b
},
},
maxSize: maxSize,
}
}
func (bpool *bufferPool) get() []byte {
return bpool.bp.Get().([]byte)
}
func (bpool *bufferPool) put(buf []byte) {
if maxSize != 0 && cap(buf) > maxSize {
return
}
// reset length to 0
bpool.bp.Put(buf[:0])
}
总结:
grpc gateway在使用sync.pool缓冲[]byte后,gc的延迟有锁下降。另外,编译的golang版本需要注意下。在1.12.x版之后。sync.pool增长了对象存活周期,规避因为gc清理后,造成的各类竞争,大概延长了两个gc左右。
在go 1.13的版本里,sync.pool又优化了锁竞争的问题,性能方面值得期待。
注意: 在调试grpc网关时,发现一个性能优化点。在grpc里加入连接池,grpc的吞吐方面性能是有提升的,因为虽然grpc基于http2是可以多路复用,但问题http2也是存在tcp协议层的对头阻塞。另外,golang的net/http实现的http2存在各种的锁竞争。对的,还是锁。