golang 的自制Pool中连接使用笔记

问题:

在用公司同事写的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 :含义是总计的访问时间

下边是不完全统计:



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值