Code前端首页关于Code前端联系我们

Golang数据库连接池技术原理与实现

terry 2年前 (2023-09-24) 阅读数 60 #后端开发

1、为什么需要连接池

如果不使用连接池,每个请求创建一个连接的成本会比较高,所以必须完成3次TCP握手。同时,在高并发场景下,由于连接池的最大连接数没有限制,因此可能会创建无数的连接,导致文件描述符被耗尽。连接池的目的是重用已创建的连接。

2。连接池设计

连接池基本上会设计以下参数: 初始连接数 :连接池初始化时预先创建的连接数。如果设置:
  • 太大:可能会造成浪费
  • 太小:请求到达时需要建立新连接
最大空闲连接数 maxIdle:当请求到达时,池中缓存的连接的最大数量设置:
  • 过大:造成浪费,不需要的时候还是检查连接。因为数据库的总连接数是有限的,如果当前进程比较忙,其他进程可能会收到更少
  • 太小:无法应对突发流量
最大连接数 maxCap :
  • 如果使用 MaxCap 连接。当申请第 maxCap+1 个连接时,一般会阻塞在那里,直到超时或另一个连接返回
最大空闲时间idleTimeout:如果发现某个连接空闲时间超过这个时间,就会被关闭后会再次获取连接
  • 避免连接长期不用而自动失效的问题
连接池提供了两种外部方法,Get:获取连接,Set:返回连接。大多数连接池的实现都是类似的,基本流程如下:

Golang数据库连接池技术原理与实现

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前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门