一、为什么需要连接池
redis使用之前需要建立连接,建立和断开的网络通信回占用大量时间 连接池可以建立多个连接不释放,使用的时候从连接池获取,用完还给连接池,免去了数据库连接所占用的时间
二、连接池的实现
以下为 go-redis 中连接池的实现
整体流程:
1)初始化redis连接池,创建指定的最小连接数的连接进行存储,并周期性的检查连接是否过期 newConnPool()
2)从连接池取连接 Get()
2.1) 先排队获取令牌 waitTurn(),令牌数量由poolSize决定
2.2) 尝试从 idleConns 中取一个空闲的连接 popIdle() 返回 或者 新创建一个连接 _NewConn(true) 并记录到 conns 中 标记 pooled=true or false
3)归还连接 Put()
3.1)通过 pooled 判断是否需要还给连接池 不用归还时从conns中移除 并调用 freeTurn() 归还令牌 需要归还的连接放入 idleConns
2.1 连接池结构
type ConnPool struct {
opt *Options
queue chan struct{} //令牌 控制连接数
connsMu sync.Mutex
conns []*Conn //保存所有连接
idleConns []*Conn //空闲连接
idleConnsLen int //空闲连接数
poolSize int //池的大小
_closed uint32 //atomic
}
type Options struct {
PoolSize int //连接池的大小
MinIdleConns int //最小空闲连接数
MaxConnAge time.Duration //连接(从建立到过期)允许的最大时间
IdleTimeout time.Duration //连接允许的最大空闲时间
PoolTimeout time.Duration //get conn 的超时时间
IdleCheckFrequency time.Duration //检查过期连接的周期
}
type Conn struct {
netConn net.Conn
pooled bool //标志着连接是否需要归还给池
createdAt time.Time
usedAt atomic.Value
}
2.2 初始化连接池
// 初始化连接池 redis newClient的时候会调用
func NewConnPool(opt *Options) *ConnPool {
p := &ConnPool{
opt: opt,
queue: make(chan struct{}, opt.PoolSize),
conns: make([]*Conn, opt.PoolSize),
idleConns: make([]*Conn, opt.PoolSize),
}
//连接池初始化的时候,往连接池填满空闲的连接
for i := 0; i < opt.MinIdleConns; i++ {
p.checkMinIdleConns()
}
//周期性检查,移除过期连接
if opt.IdleTimeout > 0 && opt.IdleCheckFrequency > 0 {
go p.reaper(opt.IdleCheckFrequency)
}
return p
}
func (p *ConnPool) checkMinIdleConns() {
if p.opt.MinIdleConns == 0 {
return
}
//连接池size比opt指定的连接池size小 并且 空闲连接数比opt指定的最小空闲数小
if p.poolSize < p.opt.PoolSize && p.idleConnsLen < p.opt.MinIdleConns {
p.poolSize++
p.idleConnsLen++
go p.addIdleConn()
}
}
func (p *ConnPool) addIdleConn() {
cn, err := p.newConn(true)
if err != nil {
return
}
p.connsMu.Lock()
p.conns = append(p.conns, cn)
p.idleConns = append(p.idleConns, cn)
p.connsMu.Unlock()
}
func (p *ConnPool) reaper(frequency time.Duration) {
ticker := time.NewTicker(frequency)
defer ticker.Stop()
for range ticker.C {
//连接池已经关闭,不进行归还了
if p.closed() {
break
}
//收回旧的连接
n, err := p.ReapStaleConns()
if err != nil {
internal.Logf("ReapStaleConns failed: %s", err)
continue
}
atomic.AddUint32(&p.stats.StaleConns, uint32(n))
}
}
func (p *ConnPool) ReapStaleConns() (int, error) {
var n int
for {
p.getTurn()
p.connsMu.Lock()
//取空闲连接数组中的第一个,判断如果没过期,返回nil,过期返回这个连接
cn := p.reapStaleConn()
p.connsMu.Unlock()
//过期的空闲连接需要被移除
if cn != nil {
p.removeConn(cn)
}
p.freeTurn()
if cn != nil {
p.closeConn(cn)
n++
} else {
break
}
}
return 1, nil
}
func (p *ConnPool) getTurn() {
p.queue <- struct{}{}
}
func (p *ConnPool) freeTurn() {
<-p.queue
}
func (p *ConnPool) closed() bool {
return atomic.LoadUint32(&p._closed) == 1
}
2.3 新建连接
func (p *ConnPool) newConn(pooled bool) (*Conn, error) {
if p.closed() {
return nil, ErrClosed
}
//... 省略部分代码
netConn, err := p.opt.Dialer()
if err != nil {
//... 省略部分代码
return nil, err
}
cn := NewConn(netConn)
cn.pooled = pooled
return cn, nil
}
func NewConn(netConn net.Conn) *Conn {
cn := &Conn{
netConn: netConn,
createdAt: time.Now(),
}
//... 省略部分代码
cn.SetUsedAt(time.Now())
return cn
}
2.4 获取连接
func (p *ConnPool) Get() (*Conn, error) {
if p.closed() {
return nil, ErrClosed
}
//1.先排队获取令牌
err := p.waitTurn()
if err != nil {
return nil, err
}
//2.pop一个空闲连接
for {
//2.1从idleConns中pop一个空闲连接
p.connsMu.Lock()
cn := p.popIdle()
p.connsMu.Unlock()
if cn == nil {
break
}
//2.2如果有空闲连接 判断是否是超时的连接 超时close掉连接 继续循环取空闲连接
if p.isStaleConn(cn) {
_ = p.CloseConn(cn)
continue
}
atomic.AddUint32(&p.stats.Hits, 1)
return cn, nil
}
atomic.AddUint32(&p.stats.Misses, 1)
//3.如果没有空闲连接 创建一个新的
newcn, err := p._NewConn(true)
if err != nil {
//归还令牌
p.freeTurn()
return nil, err
}
return newcn, nil
}
func (p *ConnPool) waitTurn() error {
select {
case p.queue <- struct{}{}:
return nil
default:
timer := timers.Get().(*time.Timer)
timer.Reset(p.opt.PoolTimeout)
select {
case p.queue <- struct{}{}:
if !timer.Stop() { // timer已经被关闭,把chan中的数据读出来
<-timer.C
timers.Put(timer)
return nil
}
case <-timer.C:
timers.Put(timer)
atomic.AddUint32(&p.stats.Timeouts, 1)
return ErrPoolTimeout
}
}
return nil
}
func (p *ConnPool) popIdle() *Conn {
if len(p.idleConns) <= 0 {
return nil
}
idx := len(p.idleConns) - 1
cn := p.idleConns[idx]
p.idleConns = p.idleConns[:idx]
p.idleConnsLen--
p.checkMinIdleConns()
return cn
}
func (p *ConnPool) _NewConn(pooled bool) (*Conn, error) {
cn, err := p.newConn(pooled)
if err != nil {
return nil, err
}
p.connsMu.Lock()
p.conns = append(p.conns, cn)
if pooled {
if p.poolSize < p.opt.PoolSize {
p.poolSize++
} else {
cn.pooled = false
}
}
p.connsMu.Unlock()
return cn, nil
}
// 连接是否需要被回收
func (p *ConnPool) isStaleConn(cn *Conn) bool {
if p.opt.IdleTimeout == 0 && p.opt.MaxConnAge == 0 {
return false
}
now := time.Now()
if p.opt.IdleTimeout > 0 && now.Sub(cn.UsedAt()) >= p.opt.IdleTimeout {
return true
}
return true
}
func (p *ConnPool) CloseConn(cn *Conn) error {
p.removeConn(cn)
return p.closeConn(cn)
}
func (p *ConnPool) removeConn(cn *Conn) {
p.connsMu.Lock()
for i, c := range p.conns {
if c == cn {
p.conns = append(p.conns[:i], p.conns[i+1:]...)
if cn.pooled {
p.poolSize--
p.checkMinIdleConns()
}
break
}
}
p.connsMu.Unlock()
}
func (cn *Conn) UsedAt() time.Time {
return cn.usedAt.Load().(time.Time)
}
2.5 归还连接
//归还连接
func (p *ConnPool) Put(cn *Conn) {
//判断是否需要放回连接池
if !cn.pooled {
p.removeConn(cn)
return
}
p.connsMu.Lock()
p.idleConns = append(p.idleConns, cn)
p.idleConnsLen++
p.connsMu.Unlock()
p.freeTurn()
return
}