go接続プールの実装

4479 ワード

本文は主に抜粋するhttps://github.com/garyburd/redigo.gitで使用したredis接続プールのソースコードを分析し、詳細は以下の通りです.
接続プールオブジェクト構造体
type Conn interface {
    Close()
}

type idleConn struct {
    c Conn
    t time.Time     //                
}

type ConnPool struct {
    //          
    Dial         func() (Conn, error)
    
    //             
    TestOnBorrow func(c Conn, t time.Time) error
    
    MaxActive   int             //        
    MaxIdle     int             //          
    IdleTimeout time.Duration   //         
    Wait        bool            

    mu     sync.Mutex
    cond   *sync.Cond  //     
    closed bool        //        
    active int         //       
    idle   list.List   //          ,         idleConn
}

変数waitの役割:デフォルトwaitがfalseの場合、接続プールの接続オブジェクトの個数が許容最大数に達した場合、cond条件変数が信号通知を受信するまでgoroutineをブロックし、接続オブジェクトを取得し続ける.waitがtrueの場合、接続プールがいっぱいになったエラーメッセージを返します.
インタフェースソース分析
Get()接続プールでの接続の取得
func (p *ConnPool) Get() (Conn, error) {
    p.mu.Lock()

    //                  
    if timeout := p.IdleTimeout; timeout > 0 {
        for i, n := 0, p.idle.Len(); i < n; i++ {
            e := p.idle.Back()
            if e == nil {
                break
            }
            ic := e.Value.(idleConn)
            if ic.t.Add(timeout).After(time.Now()) {
                break
            }
            p.idle.Remove(e)
            p.release()
            p.mu.Unlock()
            ic.c.Close()
            p.mu.Lock()
        }
    }
    
    for {
        //                
        for i, n := 0, p.idle.Len(); i < n; i++ {
            e := p.idle.Front()
            if e == nil {
                break
            }
            ic := e.Value.(idleConn)
            p.idle.Remove(e)
            test := p.TestOnBorrow
            p.mu.Unlock()
            if test == nil || test(ic.c, ic.t) == nil {
                return ic.c, nil
            }
            ic.c.Close()
            p.mu.Lock()
            p.release()
        }

        if p.closed {
            p.mu.Unlock()
            return nil, errors.New("get on closed pool")
        }
        
        //         
        if p.MaxActive == 0 || p.active < p.MaxActive {
            dial := p.Dial
            p.active += 1
            p.mu.Unlock()
            c, err := dial()
            if err != nil {
                p.mu.Lock()
                p.release()
                p.mu.Unlock()
                c = nil
            }
            return c, err
        }

        if !p.Wait {
            p.mu.Unlock()
            return nil, errors.New("connection pool exhausted")
        }

        if p.cond == nil {
            p.cond = sync.NewCond(&p.mu)
        }
        p.cond.Wait()  //       
    }
}
p.cond.Wait():関数実行プロセスのロック解除--通知信号の取得を待つ--再ロックするため、この関数を使用する場合は先にロックする必要があります.
Put()空き接続を接続プールに戻す
func (p *ConnPool) Put(c Conn) {
    p.mu.Lock()
    if !p.closed {  //        ,          
        p.idle.PushFront(idleConn{c: c, t: time.Now()})
        if p.idle.Len() > p.MaxIdle {
            oldConn := p.idle.Remove(p.idle.Back()).(idleConn)
            oldConn.c.Close()      //         ,        
        }
    }

    if p.cond != nil {
        p.cond.Signal()
    }
    p.mu.Unlock()
}

空き接続をプールに戻すと、p.cond.Signal()から信号通知が送信され、待機しているget()関数が空き接続オブジェクトを取得できることを通知する
Close()接続プールを閉じる
func (p *ConnPool) Close() {
    p.mu.Lock()
    idle := p.idle
    p.idle.Init()
    p.closed = true
    p.active -= idle.Len()
    if p.cond != nil {
        p.cond.Broadcast() //        get()            
    }
    p.mu.Unlock()

    for e := idle.Front(); e != nil; e = e.Next() {
        e.Value.(idleConn).c.Close()   //            
    }
}

ActiveCount()アクティブな接続数を取得
func (p *ConnPool) ActiveCount() int {
    p.mu.Lock()
    active := p.active
    p.mu.Unlock()
    return active
}

release()接続オブジェクトの消去
func (p *ConnPool) release() {
    p.active -= 1
    if p.cond != nil {
        p.cond.Signal()  
    }
}

Activeが減少すると、ここのp.cond.Signal()は、ブロックされたget()関数に新しい接続オブジェクトを取得するように通知することができる.
プールの接続に使用する関連標準ライブラリ
import (
    "container/list"
    "errors"
    "sync"
    "time"
)

以上のgo実装の接続プールは参考にして使用することができて、Connインタフェースのタイプを修正したり交換したりすればいいので、問題があれば、皆さんの伝言を歓迎します.