go sync.pool []byte导致grpc解包异常

前言:

在使用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存在各种的锁竞争。对的,还是锁。


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc