golang gorm 连接池,golang连接池管理tcp

  golang gorm 连接池,golang连接池管理tcp

  目录

  微信官方账号,欢迎关注。1.如何理解数据库连接?2.连接池的工作原理。3.数据库/sql包结构。4.三个重要结构4.1,DB 4.2,driverConn 4.3,Conn 5。流程梳理5.1。首先获取数据库实例5.2。进程入口:5.3,获取连接5.4,释放连接5.5,connectionOpener 5.5.1,是什么?5.5.2.什么时候打开的?5.5.3.代码详细信息。谁将消息放入openerCh?5.5.5.注:5.6。连接清洁器。这是什么?有什么用?5.6.2,Note 5.7,ConnectionRester 5.7.1,功能6,MySQL连接池的限制7,无效连接8,连接微信官方账号的有效性,欢迎微信官方账号,欢迎关注。

  一、如何理解数据库连接数据库连接池是由客户端维护的用于存储数据库连接的池。连接在池中维护,谁使用它们,谁就使用它们。目的是减少频繁创建和关闭连接的开销。

  如何理解数据库连接,可以用这个TCP编程的演示来理解。

  为了便于理解,MySQL-Server的连接池可以被认为是这个简单的Tcp-Server。

  func main() {

  //1.监听端口2。接受连接3。打开goroutine来处理连接。

  听着,err :=net。监听( tcp , 0.0.0.0:9090 )

  如果err!=零{

  fmt。Printf(错误:%v ,err)

  返回

  }

  对于{

  康恩,呃:=听着。接受()

  如果err!=零{

  fmt。Printf(失败监听。接受:%v ,错误)

  继续

  }

  转到过程控制台(控制台)

  }

  }

  //处理网络请求

  func ProcessConn(连接网络。连接){

  //defer connect . Close()

  对于{

  bt,err:=编码器。解码(连接)

  如果err!=零{

  fmt。Printf(解码失败错误[%v],err)

  返回

  }

  s :=字符串(bt)

  fmt。Printf(从连接中读取:[%v]\n ,s)

  }

  }对于我们现在看的sql包下的连接池,可以简化为下面的tcp-client。

  连接,错误:=网络。拨号( tcp ,:9090 )

  延迟连接关闭()

  如果err!=零{

  fmt。Printf(错误:%v ,err)

  返回

  }

  //将数据编码并发送出去

  编码器。Encode(conn, hi server我在这里);

  时间的一般概念。睡眠(时间。第二*10)就是程序启动的时候,根据我们的配置,sql包里的DB会提前给我们创建几个这样的conn,然后维护它们,不要关闭()它们。当我们想用的时候,我们可以向他要。

  为什么是这个tcp演示?因为数据库连接的基础依赖于tcp连接。在tcp连接的基础上,实现客户端和服务器端的数据传输,然后封装一层mysql握手、认证和交互协议,对数据包进行解析和解解析,从而贯穿整个过程。

  二、连接池连接池建立的工作原理后台系统初始化时,会根据系统配置建立连接池。但是在接受客户机请求之前,连接并没有真正创建。在go语言中,首先注册driver _ github.com/go-sql-driver/mysql初始化DB,调用Open函数。此时并没有真正的连接,而是DB操作的数据结构。连接池中连接池的使用和管理;正在关闭连接池;正在释放连接;正在关闭连接请求队列connectionOpenerconnectionResetter连接清洁器;三。数据库/sql包结构

  Driver/driver.go:定义实现数据库驱动所需的接口,由sql包和特定驱动包实现。

  Driver/types.go:定义了数据类型别名和转换。

  转换扫描:行

  SQL . go:SQL数据库的一些常用接口和类型。包括连接池、数据类型、连接、对象和语句。

  导入“github.com/go-SQL-driver/MySQL”//特定的驱动程序包

  导入“数据库/sql”

  //初始化连接

  func initDB() (err错误){

  db,err=sql .打开( mysql , root:root @ TCP(127。0 .0 .1:3306)/测试’)

  如果呃!=零{

  恐慌(错误)

  }

  //todo不要在这里关闭它,函数一结束,延期就执行了

  //延迟数据库.关闭()

  err=db .Ping()

  如果呃!=零{

  返回错误

  }

  返回零

  }四、三个重要的结构体4.1、DB/**

  分贝是代表零个或多个基础连接池的数据库句柄。对于多个不到并发使用是安全的。

  结构化查询语言包会自动创建并释放连接。它还维护空闲连接的空闲池。

  如果数据库具有每个连接状态的概念,则可以在事务(Tx)或连接(连接)中可靠地观察到这种状态。

  调用数据库.开始之后,返回的税将绑定到单个连接。

  在事务上调用犯罪或反转后,该事务的连接将返回到分贝的空闲连接池。

  池大小可以通过SetMaxIdleConns控制。

  */

  分贝结构类型{

  //仅原子访问。在结构的顶部,以防止不对齐

  //在32位平台上。类型为时间。持续时间

  //统计使用:等待新的连接所需要的总时间

  waitDuration int64 //等待新连接的总时间。

  //由具体的数据库驱动实现的连接器

  连接器驱动器。连接器

  //numClosed是一个原子计数器,它表示

  //关闭连接. Stmt.openStmt在清洗关闭前检查它

  //stmt。半铸钢钢性铸铁(Cast Semi-Steel)中的连接。

  //关闭的连接数

  numClosed uint64

  希腊字母表中第十二个字母同步。互斥//保护下列字段

  //连接池,在去中,连接的封装结构体是:driverConn

  freeConn []*driverConn

  //连接请求的地图,钥匙是自增的int64类型的数,用于唯一标示这个请求分配的

  连接请求映射[uint 64]更改连接请求

  //类似于日志中的下一个trx_ix,下一个事物的编号

  nextRequest uint64 //在连接请求中使用的下一个密钥。

  //已经打开,或者等待打开的连接数

  numOpen int //已打开和挂起的打开连接数

  //用于发出需要新连接的信号

  //运行connectionOpener()的不到读取此更改,并

  //maybeOpenNewConnections在陈上发送(每个需要的连接发送一次)

  //db期间关闭。关闭()。结束告诉连接开启者

  //goroutine退出。

  //他是个陈,用于通知connectionOpener()协程应该打开新的连接了。

  openerCh更改结构{}

  //他是个陈,用于通知连接重置器协程:重制连接的状态。

  复位通道*驱动通道

  封闭布尔

  //依赖,键是连接、声明

  资料执行防止地图[最终关闭者]depSet

  上次放置映射[*驱动程序连接]字符串//最后一个指挥操舵的放的堆栈跟踪仅调试

  //连接池的大小,0意味着使用默认的大小2, 小于0表示不使用连接池

  maxIdle int //zero表示defaultMaxIdleConns负值表示0

  //最大打开的连接数,包含连接池中的连接和连接池之外的空闲连接, 0表示不做限制

  maxOpen int //=0表示无限制

  //连接被重用的时间,设置为0表示一直可以被重用。

  最大寿命时间。持续时间//连接可以重用的最长时间

  //他是个陈,用于通知连接清洁器协程去请求过期的连接

  //当有设置最大存活时间时才会生效

  清洁器更改结构{}

  //等待的连接总数,当最大空闲数为0时,等待计数也会一直为

  //因为最大空闲数为0,每一个请求过来都会打开一条新的连接。

  等待计数int64 //等待的连接总数。

  //释放连接时,因为连接池已满而关闭的连接总数

  //如果最大寿命没有被设置,maxIdleClosed为0

  maxIdleClosed int64 //由于空闲而关闭的连接总数。

  //因为超过了最大连接时间,而被关闭的连接总数

  maxLifetimeClosed int64 //由于最大可用限制而关闭的连接总数。

  //当分贝被关闭时,关闭连接开启器和会话重置器这两个协程

  stop func() //停止取消连接开启器和会话重置器。

  }4.2、驱动程序连接的封装结构体:driverConn

  //driverConn包装驱动程序。与互斥体连接,以

  //在对指挥操舵的所有调用期间被保持。(包括对

  //通过该连接返回的接口,如Tx、Stmt、

  //结果,行)

  /**

  驱动指挥操舵使用互斥锁包装指挥操舵包装

  */

  驱动Conn结构类型{

  //持有对整个数据库的抽象结构体

  db *DB

  创建日期时间。时间

  同步。互斥//守卫跟随

  //对应于具体的连接例如mysqlConn

  铸铁驱动程序。指挥操舵

  //标记当前连接的状态:当前连接是否已经关闭

  封闭布尔

  //标记当前连接的状态:当前连接是否最终关闭,包装ci .已调用关闭

  最终关闭bool //ci .已调用关闭

  //在这些连接上打开的声明

  openStmt map[*driverStmt]bool

  //connectionResetter返回的结果

  lastErr错误//lastError捕获会话重置器的结果。

  //由db.mu保护

  //连接是否被占用了

  使用布尔

  //在归还连接时需要运行的代码。在noteUnusedDriverStatement中添加

  onPut []func() //下次返回指挥操舵时运行的代码(保存有db.mu)

  dbmuClosed bool //与关闭的相同,但由db.mu保护,用于removeClosedStmtLocked

  }4.3、连接具体的连接:驱动程序包下的指挥操舵如下,是个接口,需要被具体的实现。

  //假设指挥操舵是有状态的。

  连接器接口类型{

  //准备返回绑定到此连接的准备好的语句。

  准备(查询字符串)(Stmt,错误)

  //关闭使任何当前的无效并可能停止

  //准备好语句和事务,标记这个

  //不再使用的连接。

  //

  //因为结构化查询语言包维护了一个

  //连接,并且只有当有多余的

  //空闲连接,驱动程序没有必要

  //自己做连接缓存。

  关闭()错误

  //开始开始并返回一个新的事务。

  //

  //已弃用:驱动程序应改为(或另外)实现ConnBeginTx .

  Begin() (Tx,错误)

  }五、流程梳理5.1、先获取分贝实例在开发中,要想获取连接,一般我们都得通过下面这段代码获取到分贝的封装结构体实例。

  通过上面的三个结构体可以看出数据库、驱动程序连接、连接的关系如下:

  所以我们的代码一般长成下面这样,先获取一个分贝结构体的实例,数据库结构体中有维护连接池、以及和创建连接,关闭连接协程通信的渠道,已经各种配置参数。

  上图中浅蓝色部分的自由连接就是空闲连接池,里面的驾驶员包下的连接接口就是具体的连接。

  /**

  * MySQL连接相关的逻辑

  */

  类型连接器结构{

  BaseInfo

  数据库*sql .分贝

  }

  func(c *连接器)Open(){

  //读取配置

  c.loadConfig()

  数据来源:=c .基础信息。根用户名: c .基本信息。根密码“@ TCP( c . base info。addr : c .基本信息。port )/ c .基本信息。数据库名称

  db,Err :=sql .打开(‘MySQL’,数据源)

  如果呃!=零{

  常见。错误(无法打开数据库数据源:[%v]错误:[%v],数据源,错误。错误())

  返回

  }

  数据库.SetMaxOpenConns(500)

  数据库.SetMaxIdleConns(200)

  c.DB=db

  Err=db .Ping()

  如果呃!=零{

  fmt .Printf(无法砰数据库错误:[%v],错误。错误())

  返回

  }

  }5.2、流程梳理入口:比如我们自己写代码时,可能会搞这样一个方法做增删改

  //插入、更新、删除

  功能(c *连接器)执行(CTX上下文。语境,

  sqlText字符串,

  参数.接口{}) (qr *QueryResults) {

  qr=QueryResults{}

  result,err :=c.DB.ExecContext(ctx,sqlText,params.)

  延迟处理异常()

  如果呃!=零{

  qr .EffectRow=0

  qr .错误=错误

  常见。错误(无法执行查询sqlText:[% v]params:[% v]err:[% v],SQL text,params,err)

  返回

  }

  qr .EffectRow,_=结果RowsAffected()

  qr .LastInsertId,_=结果LastInsertId()

  返回

  }主要是使用数据库.ExecContext()执行SQL,获取返回值。

  中强是业务代码传入的上线文,通常是做超时限制使用。

  其实这里并不是严格意义上的去执行sql,它其实是通过和MySQL-服务器之间建立的连接将结构化查询语言参数发往MySQL-服务器去解析和执行。

  输入DB。ExecContext()

  主要逻辑如下:exec()方法的主要功能是:获取连接,发送sql和参数。

  如果采集失败一次,当失败次数达到sql包预定义的常量maxbadconnretries时,会创建一个新的连接,其使用不超过maxbadconnretries,并标记为cachedOrNewConn,Get connection func(db * db)exec context(CTX context . context,querystring,args.接口{})(结果,错误){

  变量结果

  var err误差

  对于I:=0;i maxBadConnRetries我{

  res,err=db.exec(ctx,query,args,cachedOrNewConn)

  如果err!=司机。ErrBadConn

  破裂

  }

  }

  if err==driver。ErrBadConn

  return db.exec(ctx,query,args,alwaysNewConn)

  }

  返回结果,错误

  }

  跟进执行()-数据库连接(CTX,战略)

  func (db *DB) exec(ctx上下文。上下文,查询字符串,args[]接口{},策略连接策略)(结果,错误){

  //这个策略是我们告诉他是创建一个新连接还是先从缓存池中获取连接的最后一步。

  dc,err :=db.conn(ctx,strategy)

  .

  }5.3.获取连接并跟踪conn()方法。

  conn方法的返回值是driverConn,也就是上面提到的数据库连接。其功能是根据获取的策略获取数据库连接。如果正常,则返回获取的数据库连接,如果不正常,则返回错误err。

  这是conn访问连接的流程图。是按照下面的代码画的,注释写在代码上。

  //conn返回新打开或缓存的*driverConn。

  func (db *DB) conn(ctx上下文。上下文,策略上下文策略)(*驱动程序,错误){

  数据库微锁()

  //首先监控数据库是否关闭

  如果数据库关闭{

  db.mu.Unlock()

  //DB全部关闭,直接返回DBClosed错误。没有必要再次获得连接。

  返回nil,errDBClosed

  }

  //检查用户传入的上下文是否过期。

  选择{

  默认值:

  //如果用户使用ctx。Done(),它无疑会进入这个case并返回一个Ctx错误。

  凯斯-ctx。完成():

  db.mu.Unlock()

  返回零,ctx。错误()

  }

  //连接被重用的时间,如果为0,表示理论上这个连接永远不会过期,可以一直使用。

  生存期:=db.maxLifetime

  //查看空闲连接池(是一个切片)是否有空闲连接。

  numFree :=len(db.freeConn)

  //如果获取策略是先从连接池中获取,并且连接池中确实有空闲的连接,就从freeConn中获取连接使用。

  if strategy==cachedOrNewConn num free 0 {

  //假设空闲池中还有五个连接:[1,2,3,4,5]

  //取出第一个连接==1

  conn :=db.freeConn[0]

  //复制切片,从而移除第一个连接。

  copy(db.freeConn,db.freeConn[1:])

  //如果db.freeConn[1:]会让freeConn变小,那么这里就是db . free conn=db . free conn[:num free-1]

  db . free conn=db . free conn[:num free-1]

  //这里得到的连接是driverConn,实际上是真实连接driver.conn的封装。

  //基于驱动程序的附加封装层。Conn可以在驱动的基础上实现。Conn,并添加状态信息,如下所示

  conn.inUse=true

  db.mu.Unlock()

  //检查是否过期。

  如果连接过期(寿命){

  连接关闭()

  返回零,驱动程序。埃尔巴德康恩

  }

  //锁定读取lastErr以确保会话重置器完成。

  //锁定处理以确保此conn未被标记为lastErr。

  //一旦标记了这个状态,就意味着ConnectionRestter进程在重置conn的状态时出现了错误也就是说,这个连接实际上是断开的,不可用的。

  连接器锁()

  错误:=conn.lastErr

  连接器解锁()

  //如果检测到此错误,驱动程序。ErrBadConn表示连接不可用,关闭连接并返回错误。

  if err==driver。ErrBadConn

  连接关闭()

  返回零,驱动程序。埃尔巴德康恩

  }

  返回连接器,零

  }

  //没有可用的连接,或者我们被要求不要使用。如果我们不是

  //允许打开更多的连接,发出请求并等待。

  //db.maxOpen 0表示允许当前db实例打开连接。

  //db.numOpen=db.maxOpen表示db目前可以打开的连接数已经大于它可以打开的最大连接数,所以建立一个请求,等待连接。

  if db . max open 0 db . numopen=db . max open {

  //创建connRequest通道。它被缓冲以便

  //connectionOpener在等待读取req时不会阻塞。

  //建立connRequest这个通道,缓存大小为1

  //用于告诉connectionOpener需要打开一个新的连接。

  req :=make(chan connRequest,1)

  /**

  nextRequestKeyLocked函数如下:

  func(DB * DB)nextrequestkey locked()uint 64 {

  下一步:=db.nextRequest

  db.nextRequest

  下一次返回

  }

  主要功能是传输nextRequest 1,

  至于这个nextRequest的作用,我们之前已经说过了,它相当于binlog中next_trx下一个东西的东西id。

  言外之意就是这个nextRequest是递增的(因为这个代码是lock)。

  看下面的代码,返回这个自增的nextRequest作为返回值。

  然后用它作为地图的键。

  至于这张地图:

  在本文的开始,我们介绍了DB结构具有这样的属性,即连接请求的映射和键是int64类型的自增数字,

  用于标记此请求分配的唯一。

  连接请求映射[uint 64]更改连接请求

  */

  req key:=db . nextrequestkeylocked()

  //缓存这第n个请求对应的通道,开始等待合适的机会分配给他的连接。

  db.connRequests[reqKey]=req

  //等待次数增加,解锁

  数据库等待计数

  db.mu.Unlock()

  等待开始:=时间。现在()

  //使用上下文的连接请求超时。

  //输入下面的切片。

  选择{

  //如果客户端传入的上下文超时,则输入case

  凯斯-ctx。完成():

  //删除连接请求,并确保没有发送任何值

  //移除后在它上面。

  //当上下文超时时,意味着上层客户端代码想要断开连接,也就是说这个方法在收到这个信号后需要退出。

  //在这里,清除db的connRequests中的reqKey,防止他被分配连接。

  数据库微锁()

  删除(db.connRequests,reqKey)

  db.mu.Unlock()

  原子的。AddInt64( db.waitDuration,int64(time。自(等待开始)))

  //这里也尝试从请求通道获得一个可用连接。

  //执行db.putconn (ret.conn,ret.err,false),如果有的话,以便释放这个连接。

  选择{

  默认值:

  案例ret,ok :=-req:

  如果正常,返回连接!=零{

  //看到这个,你只需要知道它是用来释放连接的,就ok了。继续读,以后再杀。

  db.putConn(ret.conn,ret.err,false)

  }

  }

  //返回ctx异常。

  返回零,ctx。错误()

  //尝试从reqchannel中取出连接

  案例ret,ok :=-req:

  原子的。AddInt64( db.waitDuration,int64(time。自(等待开始)))

  //处理错误

  如果!好的

  返回nil,errDBClosed

  }

  //检测连接是否过期。如前所述,DB实例有一个维护参数maxLifeTime,0表示它永远不会过期。

  如果ret . err==nil ret . conn . expired(lifetime){

  ret.conn.Close()

  返回零,驱动程序。埃尔巴德康恩

  }

  //鲁棒性检查

  if ret.conn==nil {

  ret nil,ret.err

  }

  //锁定读取lastErr以确保会话重置器完成。

  //检查连接是否可用

  ret.conn.Lock()

  错误:=ret.conn.lastErr

  ret.conn.Unlock()

  if err==driver。ErrBadConn

  ret.conn.Close()

  返回零,驱动程序。埃尔巴德康恩

  }

  返回ret.conn,ret.err

  }

  }

  //代码可以在这里运行,以显示上面的if条件没有被命中。

  //换句话说,来到这里表明满足以下条件

  //1:当前DB实例的空闲连接池中没有空闲连接。弄清楚,如果没有从空闲池中获得连接,就想创建一个新的连接。

  //2:当前数据库实例允许打开连接

  //3:DB实例当前打开的连接数没有达到它可以打开的最大连接数。

  //记录当前DB已经打开的连接数1。

  db.numOpen //乐观地

  db.mu.Unlock()

  ci,err :=db.connector.Connect(ctx)

  如果err!=零{

  数据库微锁()

  db.numOpen - //纠正之前的乐观情绪

  db.maybeOpenNewConnections()

  db.mu.Unlock()

  返回零,错误

  }

  数据库微锁()

  //建立连接实例并返回

  dc :=driverConn{

  db: db,

  createdAt: nowFunc(),

  ci: ci,

  inUse:是的,

  }

  db.addDepLocked(dc,dc)

  db.mu.Unlock()

  返回dc,零

  }5.4.连接释放后,需要释放。

  发布的逻辑封装在DB实例中。

  db.putConn(ret.conn,ret.err,false)

  发布的流程图如下:

  流程图是根据下面的代码绘制的。

  方法细节如下:

  func(DB * DB)put conn(DC * driver conn,err error,resetSession bool) {

  //释放连接的操作锁

  数据库微锁()

  //调试信息

  如果!直流使用{

  如果调试输出{

  fmt。Printf(putConn(%v)重复项是:%s \ n \上一个是:% s ,dc,stack(),db.lastPut[dc])

  }

  死机(“sql:连接返回,但从未断开”)

  }

  如果调试输出{

  db.lastPut[dc]=stack()

  }

  //标记driverConn不可处理的状态

  dc.inUse=false

  for _,fn :=range dc.onPut {

  fn()

  }

  dc.onPut=零

  //此方法的参数中有一个参数err。

  //当会话获取到这个连接,发现它已经过期或者被标记为lastErr的时候,调用这个putConn方法的时候,会同时传入这个错误,然后在这里判断当发生连接断开的时候,这个连接不会被直接放回空闲连接池。

  if err==driver。ErrBadConn

  //不重用不好的连接。

  //因为conn被认为是坏的并被丢弃,所以处理它

  //关闭。不要在这里减少打开计数,finalClose会

  //处理好这件事。

  //这个方法的功能如下:

  //他会判断当前DB维护的map的容量,也就是前面提到的情况:当DB允许连接打开,但是当前连接数已经达到当前DB允许的最大连接数时,那么下一个获取连接的请求的处理逻辑就是建立一个req通道,放入connRequests的map中,表示正在等待连接建立。

  //换句话说,当系统繁忙,业务达到高峰的时候,那么问题就来了。现在连接断开了。为了尽量减少对业务线的影响,我们是不是要主动创建一个新的连接,放入空闲连接池?

  //db.maybeOpenNewConnections()函数主要做这个。

  //方法细节如下

  /*

  func(DB * DB)maybeOpenNewConnections(){

  numRequests:=len(db . conn requests)

  如果db.maxOpen 0 {

  numCanOpen:=db . max open-db . numopen

  如果numRequests numCanOpen {

  numRequests=numCanOpen

  }

  }

  对于numRequests 0 {

  db.numOpen //乐观地

  numRequests -

  如果数据库关闭{

  返回

  }

  //它只是将一个空的结构写入这个openerCh通道,会有一个特殊的进程负责创建连接。

  db.openerCh - struct{}{}

  }

  }

  */

  db.maybeOpenNewConnections()

  //解锁,关闭连接,返回

  db.mu.Unlock()

  华盛顿。关闭()

  返回

  }

  如果putConnHook!=零{

  putConnHook(db,dc)

  }

  //如果数据库已经关闭,标志resetSession为假

  如果数据库关闭{

  //如果连接将被关闭,则不需要重置连接。

  //防止在数据库关闭后写入resetterCh。

  //当所有的DB都关闭时,说明DB中没有连接池,当然不需要关闭连接池中的连接~

  resetSession=false

  }

  //如果DB没有关闭,则进入if代码块

  如果重置会话{

  //将dricerConn中的Conn验证转换为驱动程序。会话重置器

  if _,resetSession=dc.ci .(驱动。session resetter);重置会话{

  //在这里锁定driverConn,使其在连接重置之前不会被释放。

  //必须在将连接放入池中之前获取锁,以防止它在重置之前被取出

  华盛顿。锁定()

  }

  }

  //真正将连接放回空闲连接池

  //满足connRequest或将driverConn放入空闲池并返回true或false

  /*

  func(DB * DB)putConnDBLocked(DC * driver conn,err error) bool {

  //检测DB是否都关闭了块,直接返回flase

  如果数据库关闭{

  返回false

  }

  //如果DB当前打开的连接数大于DB可以打开的最大连接数,则返回false

  if db . max open 0 db . numopen db . max open {

  返回false

  }

  //如果地图中有库存等待连接

  if c:=len(db . conn requests);c 0 {

  var请求更改连接请求

  var reqKey uint64

  //取出地图中的第一个键

  对于reqKey,req=range db.connRequests {

  破裂

  }

  //删除此键,映射中的值

  delete(db.connRequests,reqKey) //从挂起的请求中删除。

  //重新标记此连接可用的状态

  如果错误==零{

  dc.inUse=true

  }

  //将此连接放入等待连接的会话的请求通道。

  请求-连接请求{

  控制室:华盛顿特区,

  呃:呃,

  }

  返回true

  //来到这个if,说明此时没有等待获取连接的请求,也没有发生错误,DB还没有关闭。

  } else if err==nil!数据库关闭{

  //将当前空闲连接池的大小(默认值为2)与freeConn空闲连接数进行比较。

  //表示如果空闲连接超过这个指定的阈值,就需要撤销空闲连接。

  if db . maxidleconnslocked()len(db . free conn){

  //撤回

  db.freeConn=append(db.freeConn,dc)

  数据库. startCleanerLocked()

  返回true

  }

  //如果空闲连接未达到阈值,则将此连接保持为空闲连接。

  db.maxIdleClosed

  }

  //检索空闲连接返回false

  返回false

  }

  */

  //如果成功连接放入空闲连接池,或者成功连接给等待连接的会话,这里返回true。

  //检索空闲连接返回false

  //代码详情在上面的注释里。

  已添加:=db.putConnDBLocked(dc,nil)

  db.mu.Unlock()

  //如果

  如果!已添加{

  //如果DB未关闭,则输入if

  如果重置会话{

  华盛顿。解锁()

  }

  华盛顿。关闭()

  返回

  }

  //重新检查,如果连接关闭,输入if

  如果!重置会话{

  返回

  }

  //如果负责重置conn状态的线程被阻塞,则将此driverConn标记为lastErr

  选择{

  默认值:

  //如果resetterCh阻塞,则标记该连接

  //坏,继续下去。

  dc.lastErr=driver。埃尔巴德康恩

  华盛顿。解锁()

  案例db.resetterCh - dc:

  }

  }5.5,connectionOpener5.5.1,是什么?这个connectionOpener是一个工作进程,它将尝试使用指定的通道,并负责创建数据库连接。事实上,在前面阅读获取连接的逻辑时,有两种情况会阻塞等待connectionOpener创建新连接:

  第一种方法:当获取连接的策略是先从缓存连接池中获取,但是空闲连接池中没有空闲连接时,首先DB允许打开连接,但是DB可以打开的连接数已经达到了它可以打开的在线连接数,所以它要等待一个空闲连接出现,或者一个连接被释放后, DB当前可以打开的连接数小于它可以打开的最大连接数,然后它将被阻塞并等待尝试创建连接。

  第二种:获取连接的策略不再是先从空闲缓冲池中获取连接,而是直接明确地获取第一个新连接。同样,此时DB已经打开的连接数大于它可以打开的在线连接数,它将被阻塞并等待连接被创建。

  5.5.2.什么时候打开的?c驱动程序。连接器)*DB {

  ctx,取消:=上下文。WithCancel(上下文。背景())

  db :=DB{

  连接器:c,

  openerCh: make(chan struct{},connectionRequestQueueSize),

  resetterCh:make(chan * driver conn,50岁),

  lastPut:make(map[* driver conn]string),

  conn requests:make(map[uint 64]chan conn request),

  停止:取消,

  }

  //可以看到DB实例化的时候是打开的。

  go db.connectionOpener(ctx)

  go db.connectionResetter(ctx)

  返回数据库

  }5.5.3.对于代码的细节,可以看到它一直在尝试从db的openerCh获取内容,只要一获取内容,就会调用方法打开连接。

  //在单独的goroutine中运行,在请求时打开新连接。

  func(DB * DB)connection opener(CTX上下文。上下文){

  对于{

  选择{

  凯斯-ctx。完成():

  返回

  //这里

  case -db.openerCh:

  db.openNewConnection(ctx)

  }

  }

  }5.5.4.谁将消息放入openerCh?向channl发布消息的逻辑在db的mayBeOpenNewConnections中。

  func(DB * DB)maybeOpenNewConnections(){

  //检查此映射的长度以决定是否将消息发布到opennerCh中。

  numRequests:=len(db . conn requests)

  如果db.maxOpen 0 {

  numCanOpen:=db . max open-db . numopen

  如果numRequests numCanOpen {

  numRequests=numCanOpen

  }

  }

  对于numRequests 0 {

  db.numOpen //乐观地

  numRequests -

  如果数据库关闭{

  返回

  }

  //一旦执行了此步骤,connectionOpener将侦听以创建连接。

  db.openerCh - struct{}{}

  }

  }5.5.5.注意:在DB结构中有这样一个属性

  //连接池的大小,0表示使用默认大小2,小于0表示不使用连接池。

  maxIdle int //zero表示defaultMaxIdleConns负值表示0,表示空闲连接池的默认大小。如果为0,则意味着没有缓存池,这意味着将为所有想要获得连接的请求创建一个新的conn。这个时候就不会有opnerCh,更不用说connectionOpener了。

  5.6.connectionCleaner5.6.1 .是什么?有什么用?它还以协作进程的形式存在,用于定期清理数据库连接池中过期的连接。

  func(DB * DB)startCleanerLocked(){

  if db . max lifetime 0 db . numopen 0 db . cleaner ch==nil {

  db.cleanerCh=make(chan struct{},1)

  go db . connection cleaner(db . max lifetime)

  }

  }5.6.2.注意,同样,DB中也有一个参数:maxLifetime。

  它表示数据库连接的最长生存期。如果设置为0,则意味着连接永远不会过期,即

  然所有的连接永不过期,就不会存在connectionCleaner去定时根据​​maxLifetime ​​来定时清理连接。

  它的调用时机是:需要将连接放回到连接池时调用。

  5.7、connectionRestter5.7.1、作用我们使用获取的连接的封装结构体是driverConn,其实它是会driver包下的Conn连接的又一层封装,目的是增强

  driver包下的Conn的,多出来了一些状态。当将使用完毕的连接放入连接池时,就得将这些状态清除掉。

  使用谁去清除呢?就是这个go 协程:connectionRestter

  当connectionRestter碰到错误时,会将这个conn标记为lastErr,连接使用者在使用连接时会先校验conn的诸多状态,比如出现lastErr,会返回给客户端 badConnErr

  六、MySQL连接池所受的限制数据库连接池的大小到底设置为多少,得根据业务流量以及数据库所在机器的性能综合考虑。

  mysql连接数到配置在 my.cnf中,具体的参数是max_connections。

  当业务流量异常猛烈时,很可能会出现这个问题:to many connections

  对于操纵系统内核来说,当他接受到一个tcp请求就会在本地创建一个由文件系统管理的socket文件。在linux中我们将它叫做文件句柄。

  linux为防止单一进程将系统资源全部耗费掉,会限制进程最大能打开的连接数为1024,这意味着,哪怕通过改配置文件,将mysql能打开的连接池设置为9999,事实上它能打开的文件数最多不会超过1024。

  这个问题也好解决:

  命令:设置单个进程能打开的最大连接数为65535

  ulimit -HSn 65535

  通过命令: 查看进程被限制的使用各种资源的量

  ulimit -a

  core file size: 进程崩溃是转储文件大小限制

  man loaded memort 最大锁定内存大小

  open file 能打开的文件句柄数

  这些变量定义在 /etc/security/limits.conf配置文件中。

  七、关于失效的连接情况1: 客户端主动断开

  如果是客户端主动将连接close(), 那往这些连接中写数据时会得到ErrBadConn的错误,如果此时依然可以重试,将会获取新的连接。

  代码如下:

  func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error) {

   var res Result

   var err error

   for i := 0; i maxBadConnRetries; i++ {

   res, err = db.exec(ctx, query, args, cachedOrNewConn)

   if err != driver.ErrBadConn {

   break

   }

   }

   if err == driver.ErrBadConn {

   return db.exec(ctx, query, args, alwaysNewConn)

   }

   return res, err

  }情况2: 服务端挂啦

  因为这种数据库连接底层使用的是tcp实现。(tcp本身是支持全双工的,客户端和服务端支持同时往对方发送数据)依赖诸如:校验和、确认应答和序列号机制、超时重传、连接管理(3次握手,4次挥手)、以及滑动窗口、流量控制、拥塞避免,去实现整个数据交互的可靠性,协调整体不拥挤。

  这时客户端拿着一条自认为是正常的连接,往连接里面写数据。然鹅,另一端端服务端已经挂了~,但是不幸的是,客户端的tcp连接根本感知不到~~~。

  但是当它去读取服务端的返回数据时会遇到错误:unexceptBadConn EOF

  八、连接的有效性思路1:设置连接的属性: maxLifeTime

  上面也说过了,当设置了这个属性后,DB会开启一条协程connectionCleaner,专门负责清理过期的连接。

  这在一定程度上避免了服务端将连接断掉后,客户端无感知的情况。

  maxLifeTime的值到底设置多大?参考值,比数据库的wait_timeout小一些就ok。

  思路2:主动检查连接的有效性。

  比如在连接放回到空闲连接池前ping测试。在使用连接发送数据前进行连通性测试。

  公众号首发、欢迎关注

   ©

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: