聊聊TCP串連池

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

概覽:

  • 為什麼需要串連池

  • 串連失效問題

  • database/sql 中的串連池

  • 使用串連池管理Thrift連結

以下主要使用Golang作為程式設計語言

為什麼需要串連池

我覺得使用串連池最大的一個好處就是減少串連的建立和關閉,增加系統負載能力
之前就有遇到一個問題:TCP TIME_WAIT串連數過多導致服務不可用,因為未開啟資料庫連接池,再加上mysql並發較大,導致需要頻繁的建立連結,最終產生了上萬的TIME_WAIT的tcp連結,影響了系統效能。

連結池中的的功能主要是管理一堆的連結,包括建立和關閉,所以自己在fatih/pool基礎上,改造了一下:https://github.com/silenceper/pool ,使得更加通用一些,增加的一些功能點如下:

  • 連線物件不單單是net.Conn,變為了interface{}(池中儲存自己想要的格式)

  • 增加了連結的最大空閑時間(保證了當串連空閑太久,連結失效的問題)

主要是用到了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錯誤進行了重試,預設重試次數是兩次,所以能夠保證即便是連結失效或者斷開了,本次的請求能夠正常響應(繼續往下看就是分析了)。

串連失效的特徵

  • 對串連進行read讀操作時,返回EOF錯誤

  • 對串連進行write操作時,返回write tcp 127.0.0.1:52089->127.0.0.1:8002: write: broken pipe錯誤

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/

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.