1.连接池的失败重试机制
go从连接池中获取连接时有两个策略:
alwaysNewConn
:请求新的连接
cachedOrNewConn
:从连接池中获取或请求新的连接
在获取连接时go有个失败重试机制,在以下方法中:
func (db *DB) PingContext(ctx context.Context) error {
var dc *driverConn
var err error
// maxBadConnRetries是个静态变量为2,这里最多会执行两次从连接池中获取连接,如果在两次获取
// 过程中获取到可用连接则直接返回
for i := 0; i < maxBadConnRetries; i++ {
dc, err = db.conn(ctx, cachedOrNewConn)
// var ErrBadConn = errors.New("driver: bad connection")
// 标记是否是一个bad连接
if err != driver.ErrBadConn {
break
}
}
// 如果两次都获取不到可用连接,则以请求获取一个新连接的方式获取并返回
if err == driver.ErrBadConn {
dc, err = db.conn(ctx, alwaysNewConn)
}
if err != nil {
return err
}
return db.pingDC(ctx, dc, dc.releaseConn)
}
这个bad连接是在真正从连接池取出或新建,并且使用后,如果mysql server端报错,才会将这个dc的lastErr属性标记为ErrBadConn
,在下次被取到时通过判断该属性值来判断是否可用。
2.连接池如何清理bad连接
通常被标记为ErrBadConn
的连接都会被直接close掉,那为什么连接池中还会有ErrBadConn
的连接?这个问题就关系到DB结构体的resetterCh
(用于清理已用完连接的session)属性了,默认创建大小是50,如果大量请求同时释放连接,要将连接put进连接池,由于resetterCh
大小固定,消费不过来,就暂时将连接线放回连接池,并标记为ErrBadConn
,方法如下:
func (db *DB) putConn(dc *driverConn, err error, resetSession bool) {
.....
select {
default:
// 这里就是对dc暂时标记为ErrBadConn
dc.lastErr = driver.ErrBadConn
dc.Unlock()
case db.resetterCh <- dc:
}
}
3.连接池清理了bad连接后numOpen又是如何保证准确的
ErrBadConn
的连接在被close的时候,在Close()
方法中会调用removeDepLocked()
方法移除该连接上的依赖属性(包括rows、stms、txs),在这个方法中会调用相对应的finalClose接口方法来处理,方法如下:
func (db *DB) removeDepLocked(x finalCloser, dep interface{}) func() error {
// db.dep会保存DB中的依赖
xdep, ok := db.dep[x]
if !ok {
panic(fmt.Sprintf("unpaired removeDep: no deps for %T", x))
}
l0 := len(xdep)
delete(xdep, dep)
switch len(xdep) {
case l0:
// 没删除成功直接panic
panic(fmt.Sprintf("unpaired removeDep: no %T dep on %T", dep, x))
case 0:
// 依赖已经全部被删除
delete(db.dep, x)
return x.finalClose
default:
// 不为0,依赖还存在,返回nil,不执行finalClose,连接继续保留
return func() error { return nil }
}
}
再看下finalClose接口方法:
func (dc *driverConn) finalClose() error {
......
dc.db.mu.Lock()
// 这里会执行numOpen--,保证numOpen能准确统计当前连接数
dc.db.numOpen--
dc.db.maybeOpenNewConnections()
dc.db.mu.Unlock()
atomic.AddUint64(&dc.db.numClosed, 1)
return err
}