go-redis 连接池源码分析

1、建立连接和关闭连接 2、池子里面取Conn的管理 3、监控统计 4、整个Pooler池子的关闭

结构体
type ConnPool struct {
	opt *Options          //初始化的配置项
 
	dialErrorsNum uint32 // atomic 连接错误次数
 
	lastDialError   error  //连接错误的最后一次的错误类型
	lastDialErrorMu sync.RWMutex
 
	queue chan struct{}   //池子里面空闲的conn的同步channel
 
	connsMu sync.Mutex    
	conns   []*Conn       //活跃的active conns
 
	idleConnsMu sync.RWMutex
	idleConns   []*Conn   //空闲的idle conns
 
	stats Stats
 
	_closed uint32 // atomic  //池子是否关闭标签
}
初始化连接池
var _ Pooler = (*ConnPool)(nil) //接口检查
 
func NewConnPool(opt *Options) *ConnPool {
	p := &ConnPool{
		opt: opt,
 
		queue:     make(chan struct{}, opt.PoolSize), //同步用的
		conns:     make([]*Conn, 0, opt.PoolSize),
		idleConns: make([]*Conn, 0, opt.PoolSize),
	}
 
	if opt.IdleTimeout > 0 && opt.IdleCheckFrequency > 0 {
		go p.reaper(opt.IdleCheckFrequency) //定时任务,清理过期的conn
	}
 
	return p
}
 
//reaper字面意思为收割者,为清理的意思
func (p *ConnPool) reaper(frequency time.Duration) {
	ticker := time.NewTicker(frequency)
	defer ticker.Stop()
 
	for range ticker.C {
		if p.closed() {
			break
		}
        //定时清理无用的conns
		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 {
        //往channel里面写入一个,表示占用一个任务
		p.getTurn()
 
		p.idleConnsMu.Lock()
		cn := p.reapStaleConn()
		p.idleConnsMu.Unlock()
 
		if cn != nil {
			p.removeConn(cn)
		}
        
        //处理完了,释放占用的channel的位置
		p.freeTurn()
 
		if cn != nil {
			p.closeConn(cn)
			n++
		} else {
			break
		}
	}
	return n, nil
}
 
 
func (p *ConnPool) reapStaleConn() *Conn {
	if len(p.idleConns) == 0 {
		return nil
	}
    
    //取第一个空闲conn
	cn := p.idleConns[0]
    //判断是否超时没有人用
	if !cn.IsStale(p.opt.IdleTimeout) {
		return nil
	}
    
    //超时没有人用则从空闲列表里面移除 
	p.idleConns = append(p.idleConns[:0], p.idleConns[1:]...)
 
	return cn
}
 
//判断是否超时的处理
func (cn *Conn) IsStale(timeout time.Duration) bool {
	return timeout > 0 && time.Since(cn.UsedAt()) > timeout
}
 
//移除连接就是一个很简单的遍历
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:]...)
			break
		}
	}
	p.connsMu.Unlock()
}
创建连接
func (p *ConnPool) NewConn() (*Conn, error) {
	cn, err := p.newConn()
	if err != nil {
		return nil, err
	}
 
	p.connsMu.Lock()
    //创建好后加入conns中
	p.conns = append(p.conns, cn)
	p.connsMu.Unlock()
	return cn, nil
}
 
func (p *ConnPool) newConn() (*Conn, error) {
	if p.closed() {
		return nil, ErrClosed
	}
    //判断是否一直出错
	if atomic.LoadUint32(&p.dialErrorsNum) >= uint32(p.opt.PoolSize) {
		return nil, p.getLastDialError()
	}
 
	netConn, err := p.opt.Dialer()
	if err != nil {
		p.setLastDialError(err)
        //dialer出错到一定程度需要tryDial就是一个循环retry的dial
		if atomic.AddUint32(&p.dialErrorsNum, 1) == uint32(p.opt.PoolSize) {
			go p.tryDial()
		}
		return nil, err
	}
 
	return NewConn(netConn), nil
}
 
 
func (p *ConnPool) tryDial() {
	for {
		if p.closed() {
			return
		}
        //就是不断的dailer直到成功
		conn, err := p.opt.Dialer()
		if err != nil {
			p.setLastDialError(err)
			time.Sleep(time.Second)
			continue
		}
        //dialer成功,则设置dialErrorsNum为0
		atomic.StoreUint32(&p.dialErrorsNum, 0)
		_ = conn.Close()
		return
	}
}
从一个连接池中获取一个连接(GET)
func (p *ConnPool) Get() (*Conn, error) {
	if p.closed() {
		return nil, ErrClosed
	}
    
    //等待有空闲,通过channel方式同步,如果池子里面满了,则会阻塞等待,但是不会一直阻塞,有一个超时机制 
	err := p.waitTurn()
	if err != nil {
		return nil, err
	}
 
	for {
		p.idleConnsMu.Lock()
		cn := p.popIdle()
		p.idleConnsMu.Unlock()
 
		if cn == nil {
			break
		}
        //取出空闲的,并判断conn是否过期
		if cn.IsStale(p.opt.IdleTimeout) {
			p.CloseConn(cn)
			continue
		}
     
		atomic.AddUint32(&p.stats.Hits, 1)
		return cn, nil
	}
    
    //没有取到conn,miss了
	atomic.AddUint32(&p.stats.Misses, 1)
    //创建新的连接
	newcn, err := p.NewConn()
	if err != nil {
		p.freeTurn()
		return nil, err
	}
 
	return newcn, nil
}
 
//p.queue是Get方法成功的时候就往channel里面写,Put方法成功就往channel里面读,释放出来位置
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() {
                //临界点处理:防止此时正好有ticker写入了timer.C,但是select到了这一个case里面
				<-timer.C
			}
			timers.Put(timer)
			return nil
		case <-timer.C:
             //超时写入机制
			timers.Put(timer)
			atomic.AddUint32(&p.stats.Timeouts, 1)
			return ErrPoolTimeout
		}
	}
}
PUT
func (p *ConnPool) Put(cn *Conn) {
    //buffer的设置
	buf := cn.Rd.PeekBuffered()
	if buf != nil {
		internal.Logf("connection has unread data: %.100q", buf)
		p.Remove(cn)
		return
	}
 
	p.idleConnsMu.Lock()
    //放入空闲conns里面来,方便Get的时候取出来
	p.idleConns = append(p.idleConns, cn)
	p.idleConnsMu.Unlock()
    //Get成功时候是写入channel,这个时候自然是释放channel
	p.freeTurn()
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值