這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
概覽:
為什麼需要串連池
串連失效問題
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
中使用串連串連池很簡單,主要涉及下面這些配置:
db.SetMaxIdleConns(10) //串連池中最大空閑串連數 db.SetMaxOpenConns(20) //開啟的最大串連數 db.SetConnMaxLifetime(300*time.Second)//串連的最大空閑時間(可選)
註:如果MaxIdleConns
大於0並且MaxOpenConns
小於MaxIdleConns
,那麼會將MaxIdleConns
置為MaxIdleConns
來看下db這個結構,以及欄位相關說明:
type DB struct { //具體的資料庫實現的interface{}, //例如https://github.com/go-sql-driver/mysql 就註冊並並實現了driver.Open方法,主要是在裡面實現了一些鑒權的操作 driver driver.Driver //dsn串連 dsn string //在prepared statement中用到 numClosed uint64 mu sync.Mutex // protects following fields //可使用的閒置連結 freeConn []*driverConn //用來傳遞串連請求的管道 connRequests []chan connRequest //當前開啟的串連數 numOpen int //當需要建立新的連結的時候,往這個管道中發送一個struct資料, //因為在Open資料庫的就啟用了一個goroutine執行connectionOpener方法讀取管道中的資料 openerCh chan struct{} //資料庫是否已經被關閉 closed bool //用來保證鎖被正確的關閉 dep map[finalCloser]depSet //stacktrace of last conn's put; debug only lastPut map[*driverConn]string //最大空閑串連 maxIdle int //最大開啟的串連 maxOpen int //串連的最大空閑時間 maxLifetime time.Duration //定時清理空閑串連的管道 cleanerCh chan struct{}}
看一個查詢資料庫的例子:
rows, err := db.Query("select * from table1")
在調用db.Query
方法如下:
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
繼續跟進去就到了
func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error) {
方法主要是建立tcp串連,並判斷了串連的存留時間lifetime,以及串連數的一些限制,如果超過的設定的最大開啟連結數限制等待connRequest
管道中有串連產生(在putConn
釋放連結的時候就會往這個管道中寫入資料)
何時釋放連結?
當我們調用rows.Close()
的時候,就會把當前正在使用的連結重新放回freeConn
或者寫入到db.connRequests
管道中
//putConnDBLocked 方法 //如果有db.connRequests有在等待串連的話,就把當前串連給它用 if c := len(db.connRequests); c > 0 { req := db.connRequests[0] // This copy is O(n) but in practice faster than a linked list. // TODO: consider compacting it down less often and // moving the base instead? 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) { //沒人需要我這個連結,我就把他重新返回`freeConn`串連池中 db.freeConn = append(db.freeConn, dc) db.startCleanerLocked() return true }
使用串連池管理Thrift連結
這裡是使用串連池https://github.com/silenceper/pool,如何構建一個thrift連結
用戶端建立Thrift的代碼:
type Client struct { *user.UserClient}//建立Thrift用戶端連結的方法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) //defer transport.Close() if err := transport.Open(); err != nil { panic(err) } rpcClient := user.NewUserClientFactory(transport, protocolFactory) //在串連池中直接放置Client對象 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/