Golang数据库连接池技术原理与实现
1、为什么需要连接池
如果不使用连接池,每个请求创建一个连接的成本会比较高,所以必须完成3次TCP握手。同时,在高并发场景下,由于连接池的最大连接数没有限制,因此可能会创建无数的连接,导致文件描述符被耗尽。连接池的目的是重用已创建的连接。2。连接池设计
连接池基本上会设计以下参数: 初始连接数 :连接池初始化时预先创建的连接数。如果设置:- 太大:可能会造成浪费
- 太小:请求到达时需要建立新连接
- 过大:造成浪费,不需要的时候还是检查连接。因为数据库的总连接数是有限的,如果当前进程比较忙,其他进程可能会收到更少
- 太小:无法应对突发流量
- 如果使用 MaxCap 连接。当申请第 maxCap+1 个连接时,一般会阻塞在那里,直到超时或另一个连接返回
- 避免连接长期不用而自动失效的问题
3。 Golang标准库SQL连接池
Golang连接池是在标准库database/sql/sql.go下实现的。当我们运行:
db, err := sql.Open("mysql", "xxxx")
时,连接池被打开。我们可以看一下返回的DB的结构:
type DB struct {
// Atomic access only. At top of struct to prevent mis-alignment
// on 32-bit platforms. Of type time.Duration.
waitDuration int64 // 等待新连接的总时间,用于统计
connector driver.Connector // 由数据库驱动实现的连接器
// numClosed is an atomic counter which represents a total number of
// closed connections. Stmt.openStmt checks it before cleaning closed
// connections in Stmt.css.
numClosed uint64 // 关闭的连接数
mu sync.Mutex // 锁
freeConn []*driverConn // 可用连接池
connRequests map[uint64]chan connRequest // 连接请求表,key 是分配的自增键
nextRequest uint64 // 连接请求的自增键
numOpen int // 已经打开 + 即将打开的连接数
// Used to signal the need for new connections
// a goroutine running connectionOpener() reads on this chan and
// maybeOpenNewConnections sends on the chan (one send per needed connection)
// It is closed during db.Close(). The close tells the connectionOpener
// goroutine to exit.
openerCh chan struct{} // 告知 connectionOpener 需要新的连接
resetterCh chan *driverConn // connectionResetter 函数,连接放回连接池的时候会用到
closed bool
dep map[finalCloser]depSet
lastPut map[*driverConn]string // debug 时使用,记录上一个放回的连接
maxIdle int // 连接池大小,默认大小为 2,<= 0 时不使用连接池
maxOpen int // 最大打开的连接数,<= 0 不限制
maxLifetime time.Duration // 一个连接可以被重用的最大时限,也就是它在连接池中的最大存活时间,0 表示可以一直重用
cleanerCh chan struct{} // 告知 connectionCleaner 清理连接
waitCount int64 // 等待的连接总数
maxIdleClosed int64 // 释放连接时,因为连接池已满而被关闭的连接总数
maxLifetimeClosed int64 // 因为超过存活时间而被关闭的连接总数
stop func() // stop cancels the connection opener and the session resetter.
}
我们可以看到,DB连接池中存储连接的结构体freeConn并不是我们之前使用的chan,而是[]*driverConn,一个连接片。
// driverConn wraps a driver.Conn with a mutex, to
// be held during all calls into the Conn. (including any calls onto
// interfaces returned via that Conn, such as calls on Tx, Stmt,
// Result, Rows)
type driverConn struct {
db *DB // 数据库句柄
createdAt time.Time
sync.Mutex // 锁
ci driver.Conn // 对应具体的连接
closed bool // 是否标记关闭
finalClosed bool // 是否最终关闭
openStmt map[*driverStmt]bool // 在这个连接上打开的状态
lastErr error // connectionResetter 的返回结果
// guarded by db.mu
inUse bool // 连接是否占用
onPut []func() // 连接归还时要运行的函数,在 noteUnusedDriverStatement 添加
dbmuClosed bool // 和 closed 状态一致,但是由锁保护,用于 removeClosedStmtLocked
}
继续看代码,一路回到请求方法。我们可以看到这个函数:func(db*DB)conn(ctx context.Context,strategy connReuseStrategy)(*driverConn,error)。 ? 。
更快的系统响应速度: 数据库连接池 在初始化过程中,经常会创建多个数据库连接并放入池中备用。对于业务请求处理,直接使用现有的可用连接,避免了数据库连接初始化和释放过程的时间开销,从而降低了系统整体响应时间。
新的资源分配方式: 对于多个应用共享同一个数据库的系统,可以通过配置数据库连接的方式在应用层实现数据库连接池技术。设置应用程序可用的最大数据库连接数限制,防止应用程序独占所有数据库资源。
统一连接管理,避免数据库连接泄漏:在比较完善的数据库连接池实现中,可以在默认的连接忙超时设置后强制回收忙连接。这可以避免常规数据库连接操作期间可能发生的资源泄漏。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。