前段时间,因为rpc使用的conn阻塞,导致了一个非常意外的问题,静下心来学习了go的rpc包的代码。梳理了一下运行逻辑:
1. 客户端Client执行Call发出请求(内部为Call-》Go-》send)将请求向服务端打包。每次请求,都写入一个Request头,而具体业务则作为Body。
2. 服务端Server得到数据,先读取Request,并根据Request中的ServiceMethod属性,寻找之前注册的Service方法,并运行。
3. 得到的结果后,server调用call,将数据回传。数据写入之前,会根据Request对应给出Response来作为消息头。
在了解代码的过程中,发现server的代码或许是为了减少request、response头的内存GC,通过指针的方式做了一个简单的pool。
** Request定义 **
``` code
// Request is a header written before every RPC call. It is used internally
// but documented here as an aid to debugging, such as when analyzing
// network traffic.
type Request struct {
ServiceMethod string // format: "Service.Method"
Seq uint64 // sequence number chosen by client
next *Request // for free list in Server
}
```
** Server对Request的调用与释放 **
``` code
func (server *Server) getRequest() *Request {
server.reqLock.Lock()
req := server.freeReq
if req == nil {
req = new(Request)
} else {
server.freeReq = req.next
*req = Request{}
}
server.reqLock.Unlock()
return req
}
func (server *Server) freeRequest(req *Request) {
server.reqLock.Lock()
req.next = server.freeReq
server.freeReq = req
server.reqLock.Unlock()
}
```
### 利用sync.Pool来对比源码的指针池
如果不采用指针,而使用sync.Pool,会怎么样呢?参考Pool源码,由于使用了CPU的Cache,应该速度会快一些。简单写了一个测试代码:
``` code
package buforpoint
import (
"sync"
)
type Person struct{
Name string
Age int
Next *Person
}
func (p *Person)ChangeAge(newage int){
//do null func
p.Age=newage
}
func (p *Person)Report(print bool){
if print {
fmt.Printf("%+v\n",p)
}
}
type BufStruct struct{
pool sync.Pool
}
func NewBufStruct()*BufStruct{
return &BufStruct{
pool:sync.Pool{
New:func()interface{}{
return new(Person)
},
},
}
}
func (b *BufStruct)Get()*Person{
p:= b.pool.Get().(*Person)
*p=Person{}
return p
}
func (b *BufStruct)Put(p *Person){
b.pool.Put(p)
}
type PointStruct struct{
rwmut sync.RWMutex
freePerson *Person
}
func (p *PointStruct)Get()*Person {
p.rwmut.Lock()
person :=p.freePerson
if person ==nil{
person =&Person{Name:"Alex_023",Age:38}
}else{
p.freePerson = person.Next
*person =Person{} //重新赋值
}
p.rwmut.Unlock()
return person
}
func (p *PointStruct)Put(person *Person){
p.rwmut.Lock()
person.Next=p.freePerson
p.freePerson =person
p.rwmut.Unlock()
}
func NewPointStruct()*PointStruct{
return &PointStruct{}
}
```
``` code
package buforpoint
import "testing"
func TestBufStruct(t *testing.T) {
bs:= NewPointStruct()
person:=bs.Get()
person.ChangeAge(2)
person.Report()
bs.Put(person)
}
func TestPointStruct(t *testing.T) {
ps:=NewPointStruct()
person:=ps.Get()
person.ChangeAge(2)
person.Report()
ps.Put(person)
}
func BenchmarkBufStruct(b *testing.B) {
b.ReportAllocs()
bs:= NewBufStruct()
for i:=0;i<b.N;i++{
person:=bs.Get()
person.ChangeAge(i)
person.Report()
bs.Put(person)
}
}
func BenchmarkPointStruct(b *testing.B) {
b.ReportAllocs()
ps := NewPointStruct()
for i:=0;i<b.N;i++{
person:= ps.Get()
person.ChangeAge(i)
person.Report()
ps.Put(person)
}
}
```
测试结果区别还是很明显,利用sync.Pool会有大幅提升:
``` code
/usr/local/go/bin/go test -v forstudy/buforpoint -bench "^BenchmarkBufStruct|BenchmarkPointStruct$" -run ^$
PASS
BenchmarkBufStruct-4 50000000 29.2 ns/op 0 B/op 0 allocs/op
BenchmarkPointStruct-4 20000000 80.3 ns/op 0 B/op 0 allocs/op
ok forstudy/buforpoint 3.047s
```
当然,源代码中,指针池并不是瓶颈,更多消耗在于业务处理,这里只是因为兴趣做了个了解。
有疑问加站长微信联系(非本文作者)