概览:
为什么需要连接池
连接失效问题
database/sql 中的连接池
使用连接池管理Thrift链接
以下主要使用Golang作为编程语言
为什么需要连接池
我觉得使用连接池最大的一个好处就是减少连接的创建和关闭,增加系统负载能力,
之前就有遇到一个问题:TCP TIME_WAIT连接数过多导致服务不可用,因为未开启数据库连接池,再加上mysql并发较大,导致需要频繁的创建链接,最终产生了上万的TIME_WAIT的tcp链接,影响了系统性能。
链接池中的的功能主要是管理一堆的链接,包括创建和关闭,所以自己在[fatih/pool]()基础上,改造了一下:https://github.com/silenceper/pool ,使得更加通用一些,增加的一些功能点如下:
主要是用到了channel
来管理连接,并且能够很好的利用管道的顺序性,当需要使用的时候Get
一个连接,使用完毕之后Put
放回channel
中。
连接失效问题
使用连接池之后就不再是短连接,而是长连接了,就引发了一些问题:
1、长时间空闲,连接断开?
因为网络环境是复杂的,中间可能因为防火墙等原因,导致长时间空闲的连接会断开,所以可以通过两个方法来解决:
在[https://github.com/silenceper/pool]()就增加了一个这样最大空闲时间的参数,在连接创建或者连接被重新返回连接池中时重置,给每个连接都增加了一个连接的创建时间,在取出的时候对时间进行比较:https://github.com/silenceper/pool/blob/master/channel.go#L85
2、当服务端重启之后,连接失效?
远程服务端很有可能重启,那么之前创建的链接就失效了。客户端在使用的时候就需要判断这些失效的连接并丢弃,在database/sql
中就判断了这些失效的连接,使用这种错误表示var ErrBadConn = errors.New("driver: bad connection")
另外值得一提的就是在database/sql
对这种ErrBadConn
错误进行了重试,默认重试次数是两次,所以能够保证即便是链接失效或者断开了,本次的请求能够正常响应(继续往下看就是分析了)。
连接失效的特征
database/sql 中的连接池
在database/sql
中使用连接连接池很简单,主要涉及下面这些配置:
1 2 3
| db.SetMaxIdleConns(10) db.SetMaxOpenConns(20) db.SetConnMaxLifetime(300*time.Second)
|
注:如果MaxIdleConns
大于0并且MaxOpenConns
小于MaxIdleConns
,那么会将MaxIdleConns
置为MaxIdleConns
来看下db这个结构,以及字段相关说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| type DB struct { driver driver.Driver dsn string numClosed uint64 mu sync.Mutex freeConn []*driverConn connRequests []chan connRequest numOpen int openerCh chan struct{} closed bool dep map[finalCloser]depSet lastPut map[*driverConn]string maxIdle int maxOpen int maxLifetime time.Duration cleanerCh chan struct{} }
|
看一个查询数据库的例子:
1
| rows, err := db.Query("select * from table1")
|
在调用db.Query
方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { var rows *Rows var err error for i := 0; i < maxBadConnRetries; i++ { rows, err = db.query(query, args, cachedOrNewConn) if err != driver.ErrBadConn { break } } if err == driver.ErrBadConn { return db.query(query, args, alwaysNewConn) } return rows, err }
|
在什么情况下会返回,可以从这里看到:
readPack,writePack
继续跟进去就到了
1
| func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error) {
|
方法主要是创建tcp连接,并判断了连接的生存时间lifetime,以及连接数的一些限制,如果超过的设定的最大打开链接数限制等待connRequest
管道中有连接产生(在putConn
释放链接的时候就会往这个管道中写入数据)
何时释放链接?
当我们调用rows.Close()
的时候,就会把当前正在使用的链接重新放回freeConn
或者写入到db.connRequests
管道中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| if c := len(db.connRequests); c > 0 { req := db.connRequests[0] copy(db.connRequests, db.connRequests[1:]) db.connRequests = db.connRequests[:c-1] if err == nil { dc.inUse = true } req <- connRequest{ conn: dc, err: err, } return true } else if err == nil && !db.closed && db.maxIdleConnsLocked() > len(db.freeConn) { db.freeConn = append(db.freeConn, dc) db.startCleanerLocked() return true }
|
使用连接池管理Thrift链接
这里是使用连接池https://github.com/silenceper/pool,如何构建一个thrift链接
客户端创建Thrift的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| type Client struct { *user.UserClient } factory := func() (interface{}, error) { protocolFactory := thrift.NewTBinaryProtocolFactoryDefault() transportFactory := thrift.NewTTransportFactory() var transport thrift.TTransport var err error transport, err = thrift.NewTSocket(rpcConfig.Listen) if err != nil { panic(err) } transport = transportFactory.GetTransport(transport) if err := transport.Open(); err != nil { panic(err) } rpcClient := user.NewUserClientFactory(transport, protocolFactory) return &Client{UserClient: rpcClient}, nil } close := func(v interface{}) error { v.(*Client).Transport.Close() return nil } poolConfig := &pool.PoolConfig{ InitialCap: 10, MaxCap: 20, Factory: factory, Close: close, IdleTimeout: 300 * time.Second, } p, err := pool.NewChannelPool(poolConfig) if err != nil { panic(err) } conn, err := p.Get() if err != nil { return nil, err } v, ok := conn.(*Client) ...使用连接调用远程方法 p.Put(conn)
|
pool连接池代码地址:https://github.com/silenceper…
原文地址:http://silenceper.com/blog/201611/%E8%81%8A%E8%81%8Atcp%E8%BF%9E%E6%8E%A5%E6%B1%A0/