redis连接池

1,872 阅读2分钟

一、为什么需要连接池

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
}

三、参考

learnku.com/articles/41…