问题:
在用公司同事写的golang的连接池用在thrift的API和后端服务进行rpc通信的时候我用curl没有出现什么问题,但是在使用http_load的时候因为没有执行put操作,即将用过的连接池返回,导致立即出现连接池资源耗尽的错误无法访问后端服务,今天想借此机会在这里分享这里的操作,不过后边我们可以使用开源的连接池也许会更健硕些。首先从两个方面:
- 连接池中的get和put都干了啥
- http_load使用参考
func (p *Pool) Put(c interface{}, forceClose bool)(err error) { if c == nil{ return nil } if reflect.ValueOf(c).IsNil() { return nil } p.mu.Lock() if !p.closed && !forceClose{ p.idle.PushFront(idleConn{t: nowFunc(), c: c}) if p.idle.Len() > p.MaxIdle { c = p.idle.Remove(p.idle.Back()).(idleConn).c } else { c = nil } } if c == nil { p.mu.Unlock() return nil } p.active -= 1 p.mu.Unlock() return p.CloseConn(c) }
说明下,这个pool并不是开源的什么框架,只是简单的一个连接管理器,我会在稍后详细说明的,先来看看第一个问题:
连接池Pool的get和put:
type Pool struct {
// create new conn
NewConn func() (c interface{}, err error)
// check the health of conn
HeartBeat func(c interface{}, t time.Time) (err error)
// close the conn
CloseConn func (c interface{}) (err error)
// Maximum number of idle connections in the pool
MaxIdle int
// Maximum number of connections allocated by the pool
// When zero, there is no limit on the number of connections in the pool
MaxActive int
// Close connections after remaining idle for this duration. If the value
// is zero, then idle connections are not closed. Applications should set
// the timeout to a value less than the server's timeout
IdleTimeout time.Duration
// mu protects fields defined below.
mu sync.Mutex
closed bool
active int
// Stack of idleConn with most recently used at the front.
idle list.List
}
其中主要是进行tcp连接建立,心跳检查,最大空闲连接数和最大允许的连接数,空闲连接数用来限制在设置的心跳检测时间内未使用的连接数量,这个目的是减少资源过多的闲置,而MaxActive就不多说了,自然是对实际连接数量的一个上限规定。
好接下来看看实例代码:
func newPool() {
LocationConnPool = &Pool{
MaxIdle: locationRpcConnPoolMaxIdle,
MaxActive: locationRpcConnPoolMaxActive,
IdleTimeout: time.Duration(rpcPoolIdleConnTimeout) * time.Second,
NewConn: func()(c interface{}, err error){
transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
transport, err := thrift.NewTSocketTimeout(locationRpcIp + ":" + locationRpcPort, time.Duration(locationRpcTimeout) * time.Millisecond)
if err != nil{
return
}
userTransport := transportFactory.GetTransport(transport)
c = locationRpc.NewLocationClientFactory(userTransport, protocolFactory)
if err = transport.Open(); err != nil{
return
}
return
},
HeartBeat: func(c interface{}, t time.Time) (err error){
if v, ok := c.(*locationRpc.LocationClient); ok{
if time.Since(t) < time.Duration(rpcPoolConnHeartbeatInterval) * time.Second{
return nil
}
err = v.Heartbeat()
}else{
err = fmt.Errorf("wrong type of loction rpc client")
}
return
},
CloseConn: func (c interface{}) (err error){
if v, ok := c.(*locationRpc.LocationClient); ok{
v.Transport.Close()
}else{
err = fmt.Errorf("wrong type of location rpc client")
}
return
},
}
}
这个实例很简单,就只申请一个thrift的客户端TCP类型的连接而已,其它参数在这里只是初始化定义,再看:现在有一个web请求访问了这个微服务模块,这个微服务模块我们叫它API层,现在API在收到web请求后需要获得一个thrift client实例才能访问后端server,看下这块的代码:
locationRpcClient, err := apiBase.GetLocationClient()
实际调用:
// get prunes stale connections and returns a connection from the idle list or
// creates a new connection.
func (p *Pool) get() (c interface{}, err error) {
p.mu.Lock()
// 清除超时连接
if timeout := p.IdleTimeout; timeout > 0 {
for i, n := 0, p.idle.Len(); i < n; i++ {
e := p.idle.Back()
if e == nil {
break
}
ic := e.Value.(idleConn)
if ic.t.Add(timeout).After(nowFunc()) {
break
}
p.idle.Remove(e)
p.active -= 1
p.mu.Unlock()
p.CloseConn(ic.c)
p.mu.Lock()
}
}
// Get idle connection.
for i, n := 0, p.idle.Len(); i < n; i++ {
e := p.idle.Front()
if e == nil {
break
}
ic := e.Value.(idleConn)
p.idle.Remove(e)
heartBeat := p.HeartBeat
p.mu.Unlock()
//检查连接是否可用
if heartBeat == nil || heartBeat(ic.c, ic.t) == nil{
return ic.c, nil
}
p.CloseConn(ic.c)
p.mu.Lock()
p.active -= 1
}
// Check for pool closed before dialing a new connection.
if p.closed {
p.mu.Unlock()
return nil, errors.New("get on closed pool")
}
// create new connection if under limit.
if p.MaxActive == 0 || p.active < p.MaxActive {
dial := p.NewConn
p.active += 1
p.mu.Unlock()
c, err := dial()
if err != nil {
p.mu.Lock()
p.active -= 1
p.mu.Unlock()
c = nil
}
return c, err
} else {
p.mu.Unlock()
err = errors.New("connection pool exhausted")
return
}
}
这里就用到了上一步初始化一个连接池时所用到的属性,流程是先清除掉超时的连接,超时连接是在idle中查找的,然后是从idle中查找可用的连接,如果能找到就不new了,否则继续判断当前已经使用的连接数是否超过了MaxActive限制,没有超过就进行新的连接建立并将active计数更新。
到这里就说明个这个简单的pool的get使用方法,现在再看下在使用完后如何将这个连接放回连接池以方便后续的请求复用呢:
func (p *Pool) Put(c interface{}, forceClose bool)(err error) {
if c == nil{
return nil
}
if reflect.ValueOf(c).IsNil() {
return nil
}
p.mu.Lock()
if !p.closed && !forceClose{
p.idle.PushFront(idleConn{t: nowFunc(), c: c})
if p.idle.Len() > p.MaxIdle {
c = p.idle.Remove(p.idle.Back()).(idleConn).c
} else {
c = nil
}
}
if c == nil {
p.mu.Unlock()
return nil
}
p.active -= 1
p.mu.Unlock()
return p.CloseConn(c)
}
这里首先检查了当前这个客户端是或否有效,利用的是反射原理,http://www.cnblogs.com/susufufu/p/7653579.html,
然后只是简单更新下idle和active的值
http_load使用参考
其实看看参数说明就知道了:
命令格式:http_load -p 并发访问进程数 -s 访问时间 需要访问的URL文件
参数其实可以自由组合,参数之间的选择并没有什么限制。比如你写成http_load -parallel 5 -seconds
300 urls.txt也是可以的。我们把参数给大家简单说明一下。
-parallel 简写-p :含义是并发的用户进程数。
-fetches 简写-f :含义是总计的访问次数
-rate 简写-p :含义是每秒的访问频率
-seconds简写-s :含义是总计的访问时间
下边是不完全统计: