github.com/XiaoMi/Gaea@v1.2.5/docs/connection-pool.md (about)

     1  # gaea proxy后端连接池的设计与实现
     2  
     3  ## 理想的连接池
     4  
     5  基于go实现连接池的方式有很多种,比如通过chan、通过map+锁的方式,但是从使用者的角度来看,一个优秀的连接池我认为有以下几个特性: 1.有最大连接数和初始连接数限制 2.实际连接数可以在上述范围内动态伸缩 3.有限时间内获取连接且连接可用。在这三个基础之上,可能还包含一些其他非必须特性比如在运行时改变连接数最大限制、暴露连接池的一些状态信息等等。gaea的连接池是基于vitess的resource pool进行封装并添加了连接重试的功能,相关代码在backend和util目录下。
     6  
     7  ## 连接池的创建、使用
     8  
     9  ### 定义
    10  
    11  ConnectionPool定义
    12  
    13  ```golang
    14  // ConnectionPool means connection pool with specific addr
    15  type ConnectionPool struct {
    16      mu          sync.RWMutex
    17      connections *util.ResourcePool
    18  
    19      addr     string
    20      user     string
    21      password string
    22      db       string
    23  
    24      charset     string
    25      collationID mysql.CollationID
    26  
    27      capacity    int // capacity of pool
    28      maxCapacity int // max capacity of pool
    29      idleTimeout time.Duration
    30  }
    31  ```
    32  
    33  NewConnectionPool定义
    34  
    35  ```golang
    36  // NewConnectionPool create connection pool
    37  func NewConnectionPool(addr, user, password, db string, capacity, maxCapacity int, idleTimeout time.Duration, charset string, collationID mysql.CollationID) *ConnectionPool {
    38      cp := &ConnectionPool{addr: addr, user: user, password: password, db: db, capacity: capacity, maxCapacity: maxCapacity, idleTimeout: idleTimeout,   charset: charset, collationID: collationID}
    39      return cp
    40  }
    41  ```
    42  
    43  Open定义
    44  
    45  ```golang
    46  // Open open connection pool without error, should be called before use the pool
    47  func (cp *ConnectionPool) Open() {
    48      if cp.capacity == 0 {
    49          cp.capacity = DefaultCapacity
    50      }
    51  
    52      if cp.maxCapacity == 0 {
    53          cp.maxCapacity = cp.capacity
    54      }
    55      cp.mu.Lock()
    56      defer cp.mu.Unlock()
    57      cp.connections = util.NewResourcePool(cp.connect, cp.capacity, cp.maxCapacity, cp.idleTimeout)
    58      return
    59  }
    60  ```
    61  
    62  每一个连接作为一个资源单位,connections存放所有的连接资源,类型为ResourcePool。capacity定义连接池的初始容量、maxCapacity定义连接池的最大容量,实际后端连接数量会根据使用情况在[0,maxCapacity]之间浮动。idleTimeout为连接空闲关闭时间,当连接不活跃时间达到该值时,连接池将会与后端mysql断开该连接并回收资源。
    63  
    64  ### 创建
    65  
    66  外部调用者通过NewConnectionPool函数创建一个连接池对象,然后通过Open函数,初始化连接池并与后端mysql建立实际的连接(有的连接池或长连接会在第一次请求时才去建立连接)。连接池的connect方法作为资源池初始化的工厂方法,所以如果你要基于该resource pool实现其他池子时,需要实现该工厂方法。
    67  
    68  ### 使用
    69  
    70  通过连接池的Get方法可以获取一个连接,Get的入口参数包含context,该context最初设计用来传入一些全局上下文,比如超时上下文,目前gaea的获取连接超时时间是固定的,所以超时上下文也是基于该context在内部构造的。为防止上层一直阻塞在获取连接处,超过getConnTimeout未获取到连接会报超时错误,从而避免发生更严重的状况。如果发生超时次数过多,可以通过配置平台调整最大连接数大小,不同namespace的最大连接数其实是一个经验值,根据不同的业务状态有所不同,支持动态实时调整。
    71  
    72  拿到连接后,连接池会主动调用tryReuse,用来保证连接是自动提交的状态。应用层需要主动调用initBackendConn,初始化与mysql有关的状态信息,包括use db、charset、session variables、sql mode等。为了提升效率会检测后端连接与前端会话的对应变量值,不一致才会进行设置,在设置的时候也是通过批量发送sql的方式,尽最大可能减少与后端mysql的网络交互。
    73  
    74  连接使用完成后,需要手动调用recycleBackendConn回收连接,注意: 事务相关的连接是在commit或者rollback的时候进行统一释放。
    75  
    76  ## 动态维护连接
    77  
    78  动态维护连接其实包含两部分,一部分维护连接池容量,不活跃的连接要主动关闭。连接池的Open函数会调用NewResourcePool函数,NewResourcePool函数会启动一个Timer定时器,定时器通过定期检测连接活跃时间与空闲时间的差值,决定是否关闭连接、回收资源从而实现动态调整连接池的容量。另一部分是保证获取到的连接是有效连接,这里在通过writeEphemeralPacket向后端mysql连接写数据时,如果报错含有"broken pipe"即连接可能无效,则会进行重试,直到连接成功或者重试次数达到三次报错,通过主动监测和连接重试,我们不需要进行定期ping后端连接,就可以保证后端连接是有效的。
    79  
    80  ## 总结
    81  
    82  gaea的连接池实现还是相对简单可依赖的,在使用gaea的过程中,最好将后端mysql的wait_timeout值设置为比gaea idleTimeout长,可以减少不必要的连接重试。