go实现连接池

本文主要是摘取 https://github.com/garyburd/redigo.git 中的使用的redis连接池源码进行分析,详情如下

连接池对象结构体

type Conn interface {
    Close()
}

type idleConn struct {
    c Conn
    t time.Time     // 放入链表中的连接对象对应的时间
}

type ConnPool struct {
    // 创建连接对象的函数
    Dial         func() (Conn, error)
    
    // 测试空闲连接对象健康情况
    TestOnBorrow func(c Conn, t time.Time) error
    
    MaxActive   int             // 允许最大连接数
    MaxIdle     int             // 允许最大空闲连接数
    IdleTimeout time.Duration   // 空闲连接存活时间
    Wait        bool            

    mu     sync.Mutex
    cond   *sync.Cond  // 条件变量
    closed bool        // 连接池关闭标志
    active int         // 活动的连接数
    idle   list.List   // 存放空闲连接的链表,空闲连接对象类型为idleConn
}

变量wait的作用: 默认wait为false时,连接池的连接对象个数达到了允许最大数时,则阻塞goroutine,直到cond条件变量收到信号通知,然后继续获取连接对象;wait为true时,则返回连接池已满错误信息

接口源码分析

Get() 获取连接池中的连接

func (p *ConnPool) Get() (Conn, 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(time.Now()) {
                break
            }
            p.idle.Remove(e)
            p.release()
            p.mu.Unlock()
            ic.c.Close()
            p.mu.Lock()
        }
    }
    
    for {
        // 从空闲连接链表中获取可用的连接
        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)
            test := p.TestOnBorrow
            p.mu.Unlock()
            if test == nil || test(ic.c, ic.t) == nil {
                return ic.c, nil
            }
            ic.c.Close()
            p.mu.Lock()
            p.release()
        }

        if p.closed {
            p.mu.Unlock()
            return nil, errors.New("get on closed pool")
        }
        
        // 获取新的连接对象
        if p.MaxActive == 0 || p.active < p.MaxActive {
            dial := p.Dial
            p.active += 1
            p.mu.Unlock()
            c, err := dial()
            if err != nil {
                p.mu.Lock()
                p.release()
                p.mu.Unlock()
                c = nil
            }
            return c, err
        }

        if !p.Wait {
            p.mu.Unlock()
            return nil, errors.New("connection pool exhausted")
        }

        if p.cond == nil {
            p.cond = sync.NewCond(&p.mu)
        }
        p.cond.Wait()  // 等待通知信号
    }
}

p.cond.Wait():函数执行过程 解锁--等待获取通知信号--重新加锁,所以使用该函数时需要先加锁

Put() 把空闲连接放回连接池

func (p *ConnPool) Put(c Conn) {
    p.mu.Lock()
    if !p.closed {  // 当连接池关闭时,跳过对连接池相关操作
        p.idle.PushFront(idleConn{c: c, t: time.Now()})
        if p.idle.Len() > p.MaxIdle {
            oldConn := p.idle.Remove(p.idle.Back()).(idleConn)
            oldConn.c.Close()      // 当空闲连接数满时,删除旧的连接对象
        }
    }

    if p.cond != nil {
        p.cond.Signal()
    }
    p.mu.Unlock()
}

在把空闲连接放回池时,p.cond.Signal()发送信号通知,通知等待的get()函数可以获取空闲的连接对象

Close() 关闭连接池

func (p *ConnPool) Close() {
    p.mu.Lock()
    idle := p.idle
    p.idle.Init()
    p.closed = true
    p.active -= idle.Len()
    if p.cond != nil {
        p.cond.Broadcast() // 通知所有等待的get()函数已经关闭了连接池信号
    }
    p.mu.Unlock()

    for e := idle.Front(); e != nil; e = e.Next() {
        e.Value.(idleConn).c.Close()   // 释放所有的空闲连接对象
    }
}

ActiveCount() 获得已经活动的连接数目

func (p *ConnPool) ActiveCount() int {
    p.mu.Lock()
    active := p.active
    p.mu.Unlock()
    return active
}

release() 清除连接对象

func (p *ConnPool) release() {
    p.active -= 1
    if p.cond != nil {
        p.cond.Signal()  
    }
}

当active减少时,这里的p.cond.Signal()可以通知阻塞的get()函数创建获取新的连接对象

连接池所用相关标准库

import (
    "container/list"
    "errors"
    "sync"
    "time"
)

以上go实现的连接池可以借鉴参考使用,只要修改或替换Conn接口类型就可以了,如有问题,欢迎大家留言指正!

你可能感兴趣的:(go实现连接池)