太长不看版
go redis客户端维护了一个连接池,新请求会尝试从连接池获取一个连接,超过一定时间取不到就会报这个错。
源码分析
go redis客户端维护了一个连接池,这样可以复用连接,减少系统开销 (池化技术介绍)
- 先看连接池有哪些接口,关键的是Get和Put
- 当有新请求进来时,调用Get接口获取*Conn
- 当完成一个请求时,调用Put把连接放回池子
type Pooler interface {
NewConn(context.Context) (*Conn, error)
CloseConn(*Conn) error
Get(context.Context) (*Conn, error)
Put(context.Context, *Conn)
Remove(context.Context, *Conn, error)
Len() int
IdleLen() int
Stats() *Stats
Close() error
}
- 来看连接池的具体实现
- queue这个channel用来控制连接数,被设置成连接池大小
- 当尝试获取一个连接,就往里面塞入一个元素
- 如果queue满了,就会造成无法写入,一定时间后产生超时
type ConnPool struct {
cfg *Options
dialErrorsNum uint32 // atomic
lastDialError atomic.Value
queue chan struct{}
connsMu sync.Mutex
conns []*Conn
idleConns []*Conn
poolSize int
idleConnsLen int
stats Stats
_closed uint32 // atomic
}
- 新建连接池时会把queue这个channel初始化成连接池大小
func NewConnPool(opt *Options) *ConnPool {
p := &ConnPool{
cfg: opt,
queue: make(chan struct{}, opt.PoolSize),
conns: make([]*Conn, 0, opt.PoolSize),
idleConns: make([]*Conn, 0, opt.PoolSize),
}
p.connsMu.Lock()
p.checkMinIdleConns()
p.connsMu.Unlock()
return p
}
- 获取连接时会先尝试p.queue <- struct{}{}占位
- 超时则报ErrPoolTimeout,也就是【redis: connection pool timeout】`
func (p *ConnPool) waitTurn(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
select {
case p.queue <- struct{}{}:
return nil
default:
}
timer := timers.Get().(*time.Timer)
timer.Reset(p.cfg.PoolTimeout)
select {
case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
timers.Put(timer)
return ctx.Err()
case p.queue <- struct{}{}:
if !timer.Stop() {
<-timer.C
}
timers.Put(timer)
return nil
case <-timer.C:
timers.Put(timer)
atomic.AddUint32(&p.stats.Timeouts, 1)
return ErrPoolTimeout
}
}
- 释放连接时会从queue中释放一个空位
func (p *ConnPool) freeTurn() {
<-p.queue
}