Golang連接池的幾種實(shí)現(xiàn)案例

因?yàn)門(mén)CP的三只握手等等原因,建立一個(gè)連接是一件成本比較高的行為姨丈。所以在一個(gè)需要多次與特定實(shí)體交互的程序中翁潘,就需要維持一個(gè)連接池拜马,里面有可以復(fù)用的連接可供重復(fù)使用俩莽。

而維持一個(gè)連接池,最基本的要求就是要做到:thread safe(線程安全)出刷,尤其是在Golang這種特性是goroutine的語(yǔ)言中馁龟。

實(shí)現(xiàn)簡(jiǎn)單的連接池

type Pool struct {

m sync.Mutex // 保證多個(gè)goroutine訪問(wèn)時(shí)候屁柏,closed的線程安全

res chan io.Closer //連接存儲(chǔ)的chan

factory func() (io.Closer,error) //新建連接的工廠方法

closed bool //連接池關(guān)閉標(biāo)志

}

這個(gè)簡(jiǎn)單的連接池淌喻,我們利用chan來(lái)存儲(chǔ)池里的連接八拱。而新建結(jié)構(gòu)體的方法也比較簡(jiǎn)單:

func New(fn func() (io.Closer, error), size uint) (*Pool, error) {

if size <= 0 {

return nil, errors.New("size的值太小了肌稻。")

}

return &Pool{

factory: fn,

res:? ? make(chan io.Closer, size),

}, nil

}

只需要提供對(duì)應(yīng)的工廠函數(shù)和連接池的大小就可以了。

獲取連接

那么我們要怎么從中獲取資源呢诺凡?因?yàn)槲覀儍?nèi)部存儲(chǔ)連接的結(jié)構(gòu)是chan腹泌,所以只需要簡(jiǎn)單的select就可以保證線程安全:

//從資源池里獲取一個(gè)資源

func (p *Pool) Acquire() (io.Closer,error) {

select {

case r,ok := <-p.res:

log.Println("Acquire:共享資源")

if !ok {

return nil,ErrPoolClosed

}

return r,nil

default:

log.Println("Acquire:新生成資源")

return p.factory()

}

}

我們先從連接池的res這個(gè)chan里面獲取,如果沒(méi)有的話我們就利用我們?cè)缫呀?jīng)準(zhǔn)備好的工廠函數(shù)進(jìn)行構(gòu)造連接专甩。同時(shí)我們?cè)趶膔es獲取連接的時(shí)候利用ok先確定了這個(gè)連接池是否已經(jīng)關(guān)閉配深。如果已經(jīng)關(guān)閉的話我們就返回早已經(jīng)準(zhǔn)備好的連接已關(guān)閉錯(cuò)誤篓叶。

關(guān)閉連接池

那么既然提到關(guān)閉連接池,我們是怎么樣關(guān)閉連接池的呢俐镐?

//關(guān)閉資源池佩抹,釋放資源

func (p *Pool) Close() {

p.m.Lock()

defer p.m.Unlock()

if p.closed {

return

}

p.closed = true

//關(guān)閉通道无宿,不讓寫(xiě)入了

close(p.res)

//關(guān)閉通道里的資源

for r:=range p.res {

r.Close()

}

}

這邊我們需要先進(jìn)行p.m.Lock()上鎖操作孽鸡,這么做是因?yàn)槲覀冃枰獙?duì)結(jié)構(gòu)體里面的closed進(jìn)行讀寫(xiě)。需要先把這個(gè)標(biāo)志位設(shè)定后奥洼,關(guān)閉res這個(gè)chan皮迟,使得Acquire方法無(wú)法再獲取新的連接。我們?cè)賹?duì)res這個(gè)chan里面的連接進(jìn)行Close操作尉尾。

釋放連接

釋放連接首先得有個(gè)前提沙咏,就是連接池還沒(méi)有關(guān)閉。如果連接池已經(jīng)關(guān)閉再往res里面送連接的話就好觸發(fā)panic吆豹。

func (p *Pool) Release(r io.Closer){

//保證該操作和Close方法的操作是安全的

p.m.Lock()

defer p.m.Unlock()

//資源池都關(guān)閉了,就省這一個(gè)沒(méi)有釋放的資源了衷快,釋放即可

if p.closed {

r.Close()

return

}

select {

case p.res <- r:

log.Println("資源釋放到池子里了")

default:

log.Println("資源池滿(mǎn)了蘸拔,釋放這個(gè)資源吧")

r.Close()

}

}

以上就是一個(gè)簡(jiǎn)單且線程安全的連接池實(shí)現(xiàn)方式了宝冕。我們可以看到的是,現(xiàn)在連接池雖然已經(jīng)實(shí)現(xiàn)了湿刽,但是還有幾個(gè)小缺點(diǎn):

我們對(duì)連接最大的數(shù)量沒(méi)有限制诈闺,如果線程池空的話都我們默認(rèn)就直接新建一個(gè)連接返回了。一旦并發(fā)量高的話將會(huì)不斷新建連接仁烹,很容易(尤其是MySQL)造成too many connections的報(bào)錯(cuò)發(fā)生卓缰。

既然我們需要保證最大可獲取連接數(shù)量,那么我們就不希望數(shù)量定的太死总寒。希望空閑的時(shí)候可以維護(hù)一定的空閑連接數(shù)量idleNum摄闸,但是又希望我們能限制最大可獲取連接數(shù)量maxNum。

第一種情況是并發(fā)過(guò)多的情況画切,那么如果并發(fā)量過(guò)少呢霍弹?現(xiàn)在我們?cè)谛陆ㄒ粋€(gè)連接并且歸還后岛宦,我們很長(zhǎng)一段時(shí)間不再使用這個(gè)連接砾肺。那么這個(gè)連接很有可能在幾個(gè)小時(shí)甚至更長(zhǎng)時(shí)間之前就已經(jīng)建立的了。長(zhǎng)時(shí)間閑置的連接我們并沒(méi)有辦法保證它的可用性裙盾。便有可能我們下次獲取的連接是已經(jīng)失效的連接。

那么我們可以從已經(jīng)成熟使用的MySQL連接池庫(kù)和Redis連接池庫(kù)中看看徘熔,它們是怎么解決這些問(wèn)題的近顷。

Golang標(biāo)準(zhǔn)庫(kù)的Sql連接池

Golang的連接池實(shí)現(xiàn)在標(biāo)準(zhǔn)庫(kù)database/sql/sql.go下慕匠。當(dāng)我們運(yùn)行:

db, err := sql.Open("mysql","xxxx")

的時(shí)候,就會(huì)打開(kāi)一個(gè)連接池锅铅。我們可以看看返回的db的結(jié)構(gòu)體:

type DB struct {

waitDuration int64 // Total time waited for new connections.

mu? ? ? ? ? sync.Mutex // protects following fields

freeConn? ? []*driverConn

connRequests map[uint64]chan connRequest

nextRequest? uint64 // Next key to use in connRequests.

numOpen? ? ? int? ? // number of opened and pending open connections

// Used to signal the need for new connections

// a goroutine running connectionOpener() reads on this chan and

// maybeOpenNewConnections sends on the chan (one send per needed connection)

// It is closed during db.Close(). The close tells the connectionOpener

// goroutine to exit.

openerCh? ? ? ? ? chan struct{}

closed? ? ? ? ? ? bool

maxIdle? ? ? ? ? int? ? ? ? ? ? ? ? ? ? // zero means defaultMaxIdleConns; negative means 0

maxOpen? ? ? ? ? int? ? ? ? ? ? ? ? ? ? // <= 0 means unlimited

maxLifetime? ? ? time.Duration? ? ? ? ? // maximum amount of time a connection may be reused

cleanerCh? ? ? ? chan struct{}

waitCount? ? ? ? int64 // Total number of connections waited for.

maxIdleClosed? ? int64 // Total number of connections closed due to idle.

maxLifetimeClosed int64 // Total number of connections closed due to max free limit.

}

上面省去了一些暫時(shí)不需要關(guān)注的field漆腌。我們可以看的,DB這個(gè)連接池內(nèi)部存儲(chǔ)連接的結(jié)構(gòu)freeConn,并不是我們之前使用的chan匆骗,而是**[]driverConn**碉就,一個(gè)連接切片。同時(shí)我們還可以看到骏庸,里面有maxIdle等相關(guān)變量來(lái)控制空閑連接數(shù)量。值得注意的是一姿,DB的初始化函數(shù)Open函數(shù)并沒(méi)有新建數(shù)據(jù)庫(kù)連接。而新建連接在哪個(gè)函數(shù)呢蛉顽?我們可以在Query方法一路往回找,我們可以看到這個(gè)函數(shù):func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn, error)曾棕。而我們從連接池獲取連接的方法,就從這里開(kāi)始:

獲取連接

// conn returns a newly-opened or cached *driverConn.

func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn, error) {

? ? // 先判斷db是否已經(jīng)關(guān)閉瀑凝。

db.mu.Lock()

if db.closed {

db.mu.Unlock()

return nil, errDBClosed

}

// 注意檢測(cè)context是否已經(jīng)被超時(shí)等原因被取消粤咪。

select {

default:

case <-ctx.Done():

db.mu.Unlock()

return nil, ctx.Err()

}

lifetime := db.maxLifetime

// 這邊如果在freeConn這個(gè)切片有空閑連接的話,就left pop一個(gè)出列囊拜。注意的是,這邊因?yàn)槭乔衅僮髅弁校孕枰懊嫘枰渔i且獲取后進(jìn)行解鎖操作。同時(shí)判斷返回的連接是否已經(jīng)過(guò)期蜂挪。

numFree := len(db.freeConn)

if strategy == cachedOrNewConn && numFree > 0 {

conn := db.freeConn[0]

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

db.freeConn = db.freeConn[:numFree-1]

conn.inUse = true

db.mu.Unlock()

if conn.expired(lifetime) {

conn.Close()

return nil, driver.ErrBadConn

}

// Lock around reading lastErr to ensure the session resetter finished.

conn.Lock()

err := conn.lastErr

conn.Unlock()

if err == driver.ErrBadConn {

conn.Close()

return nil, driver.ErrBadConn

}

return conn, nil

}

// 這邊就是等候獲取連接的重點(diǎn)了刺覆。當(dāng)空閑的連接為空的時(shí)候,這邊將會(huì)新建一個(gè)request(的等待連接 的請(qǐng)求)并且開(kāi)始等待

if db.maxOpen > 0 && db.numOpen >= db.maxOpen {

// 下面的動(dòng)作相當(dāng)于往connRequests這個(gè)map插入自己的號(hào)碼牌伦仍。

// 插入號(hào)碼牌之后這邊就不需要阻塞等待繼續(xù)往下走邏輯充蓝。

req := make(chan connRequest, 1)

reqKey := db.nextRequestKeyLocked()

db.connRequests[reqKey] = req

db.waitCount++

db.mu.Unlock()

waitStart := time.Now()

// Timeout the connection request with the context.

select {

case <-ctx.Done():

// context取消操作的時(shí)候,記得從connRequests這個(gè)map取走自己的號(hào)碼牌卑笨。

db.mu.Lock()

delete(db.connRequests, reqKey)

db.mu.Unlock()

atomic.AddInt64(&db.waitDuration, int64(time.Since(waitStart)))

select {

default:

case ret, ok := <-req:

? ? ? ? ? ? ? ? // 這邊值得注意了,因?yàn)楝F(xiàn)在已經(jīng)被context取消了桶良。但是剛剛放了自己的號(hào)碼牌進(jìn)去排隊(duì)里面陨帆。意思是說(shuō)不定已經(jīng)發(fā)了連接了,所以得注意歸還瑰步!

if ok && ret.conn != nil {

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

}

}

return nil, ctx.Err()

case ret, ok := <-req:

? ? ? ? ? ? // 下面是已經(jīng)獲得連接后的操作了。檢測(cè)一下獲得連接的狀況袁滥。因?yàn)橛锌赡芤呀?jīng)過(guò)期了等等。

atomic.AddInt64(&db.waitDuration, int64(time.Since(waitStart)))

if !ok {

return nil, errDBClosed

}

if ret.err == nil && ret.conn.expired(lifetime) {

ret.conn.Close()

return nil, driver.ErrBadConn

}

if ret.conn == nil {

return nil, ret.err

}

ret.conn.Lock()

err := ret.conn.lastErr

ret.conn.Unlock()

if err == driver.ErrBadConn {

ret.conn.Close()

return nil, driver.ErrBadConn

}

return ret.conn, ret.err

}

}

// 下面就是如果上面說(shuō)的限制情況不存在,可以創(chuàng)建先連接時(shí)候熄赡,要做的創(chuàng)建連接操作了凌箕。

db.numOpen++ // optimistically

db.mu.Unlock()

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

if err != nil {

db.mu.Lock()

db.numOpen-- // correct for earlier optimism

db.maybeOpenNewConnections()

db.mu.Unlock()

return nil, err

}

db.mu.Lock()

dc := &driverConn{

db:? ? ? ? db,

createdAt: nowFunc(),

ci:? ? ? ? ci,

inUse:? ? true,

}

db.addDepLocked(dc, dc)

db.mu.Unlock()

return dc, nil

}

簡(jiǎn)單來(lái)說(shuō),DB結(jié)構(gòu)體除了用的是slice來(lái)存儲(chǔ)連接芜壁,還加了一個(gè)類(lèi)似排隊(duì)機(jī)制的connRequests來(lái)解決獲取等待連接的過(guò)程纫溃。同時(shí)在判斷連接健康性都有很好的兼顧窖铡。那么既然有了排隊(duì)機(jī)制,歸還連接的時(shí)候是怎么做的呢箍铲?

釋放連接

我們可以直接找到func (db *DB) putConnDBLocked(dc *driverConn, err error) bool這個(gè)方法。就像注釋說(shuō)的翘瓮,這個(gè)方法主要的目的是:

Satisfy a connRequest or put the driverConn in the idle pool and return true or return false.

我們主要來(lái)看看里面重點(diǎn)那幾行:

...

// 如果已經(jīng)超過(guò)最大打開(kāi)數(shù)量了踊赠,就不需要在回歸pool了

if db.maxOpen > 0 && db.numOpen > db.maxOpen {

return false

}

// 這邊是重點(diǎn)了,基本來(lái)說(shuō)就是從connRequest這個(gè)map里面隨機(jī)抽一個(gè)在排隊(duì)等著的請(qǐng)求。取出來(lái)后發(fā)給他拔创。就不用歸還池子了。

if c := len(db.connRequests); c > 0 {

var req chan connRequest

var reqKey uint64

for reqKey, req = range db.connRequests {

break

}

delete(db.connRequests, reqKey) // 刪除這個(gè)在排隊(duì)的請(qǐng)求。

if err == nil {

dc.inUse = true

}

? ? ? ? // 把連接給這個(gè)正在排隊(duì)的連接变擒。

req <- connRequest{

conn: dc,

err:? err,

}

return true

} else if err == nil && !db.closed {

? ? ? ? // 既然沒(méi)人排隊(duì)材部,就看看到了最大連接數(shù)目沒(méi)有苦丁。沒(méi)到就歸還給freeConn。

if db.maxIdleConnsLocked() > len(db.freeConn) {

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

db.startCleanerLocked()

return true

}

db.maxIdleClosed++

}

...

我們可以看到,當(dāng)歸還連接時(shí)候淘太,如果有在排隊(duì)輪候的請(qǐng)求就不歸還給池子直接發(fā)給在輪候的人了。

現(xiàn)在基本就解決前面說(shuō)的小問(wèn)題了。不會(huì)出現(xiàn)連接太多導(dǎo)致無(wú)法控制too many connections的情況挎扰。也很好了維持了連接池的最小數(shù)量。同時(shí)也做了相關(guān)對(duì)于連接健康性的檢查操作尽超。

值得注意的是巩踏,作為標(biāo)準(zhǔn)庫(kù)的代碼,相關(guān)注釋和代碼都非常完美,真的可以看的神清氣爽在讶。

redisGolang實(shí)現(xiàn)的Redis客戶(hù)端

這個(gè)Golang實(shí)現(xiàn)的Redis客戶(hù)端,是怎么實(shí)現(xiàn)連接池的。這邊的思路非常奇妙碟嘴,還是能學(xué)習(xí)到不少好思路。當(dāng)然了,由于代碼注釋比較少,啃起來(lái)第一下還是有點(diǎn)迷糊的泊业。相關(guān)代碼地址在https://github.com/go-redis/redis/blob/master/internal/pool/pool.go 可以看到。

而它的連接池結(jié)構(gòu)如下

type ConnPool struct {

...

queue chan struct{}

connsMu? ? ? sync.Mutex

conns? ? ? ? []*Conn

idleConns? ? []*Conn

poolSize? ? int

idleConnsLen int

stats Stats

_closed? uint32 // atomic

closedCh chan struct{}

}

我們可以看到里面存儲(chǔ)連接的結(jié)構(gòu)還是slice宦搬。但是我們可以重點(diǎn)看看queue,conns憔足,idleConns這幾個(gè)變量揭绑,后面會(huì)提及到。但是值得注意的是邦蜜!我們可以看到,這里有兩個(gè)**[]Conn**結(jié)構(gòu):conns、idleConns,那么問(wèn)題來(lái)了:

到底連接存在哪里萍肆?

新建連接池連接

我們先從新建連接池連接開(kāi)始看:

func NewConnPool(opt *Options) *ConnPool {

....

p.checkMinIdleConns()

if opt.IdleTimeout > 0 && opt.IdleCheckFrequency > 0 {

go p.reaper(opt.IdleCheckFrequency)

}

....

}

初始化連接池的函數(shù)有個(gè)和前面兩個(gè)不同的地方亲铡。

1.checkMinIdleConns方法,在連接池初始化的時(shí)候就會(huì)往連接池填滿(mǎn)空閑的連接厨疙。

2.go p.reaper(opt.IdleCheckFrequency)則會(huì)在初始化連接池的時(shí)候就會(huì)起一個(gè)go程撒蟀,周期性的淘汰連接池里面要被淘汰的連接配椭。

獲取連接

func (p *ConnPool) Get(ctx context.Context) (*Conn, error) {

if p.closed() {

return nil, ErrClosed

}

? ? //這邊和前面sql獲取連接函數(shù)的流程先不同。sql是先看看連接池有沒(méi)有空閑連接,有的話先獲取不到再排隊(duì)。這邊是直接先排隊(duì)獲取令牌旺入,排隊(duì)函數(shù)后面會(huì)分析拗秘。

err := p.waitTurn(ctx)

if err != nil {

return nil, err

}

//前面沒(méi)出error的話凡涩,就已經(jīng)排隊(duì)輪候到了讹蘑。接下來(lái)就是獲取的流程版仔。

for {

p.connsMu.Lock()

? ? ? ? //從空閑連接里面先獲取一個(gè)空閑連接然想。

cn := p.popIdle()

p.connsMu.Unlock()

if cn == nil {

? ? ? ? ? ? // 沒(méi)有空閑連接時(shí)候直接跳出循環(huán)妨蛹。

break

}

// 判斷是否已經(jīng)過(guò)時(shí)表窘,是的話close掉了然后繼續(xù)取出昂验。

if p.isStaleConn(cn) {

_ = p.CloseConn(cn)

continue

}

atomic.AddUint32(&p.stats.Hits, 1)

return cn, nil

}

atomic.AddUint32(&p.stats.Misses, 1)

? ? // 如果沒(méi)有空閑連接的話甫恩,這邊就直接新建連接了松靡。

newcn, err := p.newConn(ctx, true)

if err != nil {

? ? ? ? // 歸還令牌屠列。

p.freeTurn()

return nil, err

}

return newcn, nil

}

我們可以試著回答開(kāi)頭那個(gè)問(wèn)題:連接到底存在哪里撞蜂?答案是從cn := p.popIdle()這句話可以看出浦旱,獲取連接這個(gè)動(dòng)作,是從idleConns里面獲取的,而里面的函數(shù)也證明了這一點(diǎn)。但是痪枫,真的是這樣的嘛?我們后面再看看。

同時(shí)我的理解是:

sql的排隊(duì)意味著我對(duì)連接池申請(qǐng)連接后很魂,把自己的編號(hào)告訴連接池凡纳。連接那邊一看到有空閑了,就叫我的號(hào)。我答應(yīng)了一聲好芭,然后連接池就直接給個(gè)連接給我邻薯。我如果不歸還葛作,連接池就一直不叫下一個(gè)號(hào)玖院。

redis這邊的意思是耍共,我去和連接池申請(qǐng)的不是連接而是令牌倘屹。我就一直排隊(duì)等著,連接池給我令牌了晕翠,我才去倉(cāng)庫(kù)里面找空閑連接或者自己新建一個(gè)連接。用完了連接除了歸還連接外,還得歸還令牌膳灶。當(dāng)然了,如果我自己新建連接出錯(cuò)了,我哪怕拿不到連接回家粹排,我也得把令牌給回連接池斧抱,不然連接池的令牌數(shù)少了,最大連接數(shù)也會(huì)變小弛槐。

而:

func (p *ConnPool) freeTurn() {

<-p.queue

}

func (p *ConnPool) waitTurn(ctx context.Context) error {

...

case p.queue <- struct{}{}:

return nil

...

}

就是在靠queue這個(gè)chan來(lái)維持令牌數(shù)量。

那么conns的作用是什么呢帅韧?我們可以來(lái)看看新建連接這個(gè)函數(shù):

新建連接

func (p *ConnPool) newConn(ctx context.Context, pooled bool) (*Conn, error) {

cn, err := p.dialConn(ctx, pooled)

if err != nil {

return nil, err

}

p.connsMu.Lock()

p.conns = append(p.conns, cn)

if pooled {

// 如果連接池滿(mǎn)了哑诊,會(huì)在后面移除。

if p.poolSize >= p.opt.PoolSize {

cn.pooled = false

} else {

p.poolSize++

}

}

p.connsMu.Unlock()

return cn, nil

}

基本邏輯出來(lái)了及刻。就是如果新建連接的話镀裤,我并不會(huì)直接放在idleConns里面,而是先放conns里面缴饭。同時(shí)先看池子滿(mǎn)了沒(méi)有暑劝。滿(mǎn)的話后面歸還的時(shí)候會(huì)標(biāo)記,后面會(huì)刪除颗搂。那么這個(gè)后面會(huì)刪除担猛,指的是什么時(shí)候呢?那就是下面說(shuō)的歸還連接的時(shí)候了丢氢。

歸還連接

func (p *ConnPool) Put(cn *Conn) {

if cn.rd.Buffered() > 0 {

internal.Logger.Printf("Conn has unread data")

p.Remove(cn, BadConnError{})

return

}

//這就是我們剛剛說(shuō)的后面了傅联,前面標(biāo)記過(guò)不要入池的,這邊就刪除了疚察。當(dāng)然了蒸走,里面也會(huì)進(jìn)行freeTurn操作。

if !cn.pooled {

? ? ? ? // 這個(gè)方法就是前面的標(biāo)志位貌嫡,判斷里面可以知道比驻,前面標(biāo)志不要池化的,這里會(huì)將它刪除衅枫。

p.Remove(cn, nil)

return

}

p.connsMu.Lock()

p.idleConns = append(p.idleConns, cn)

p.idleConnsLen++

p.connsMu.Unlock()

? ? //我們可以看到很明顯的這個(gè)歸還號(hào)碼牌的動(dòng)作嫁艇。

p.freeTurn()

}

答案就是朗伶,所有的連接其實(shí)是存放在conns這個(gè)切片里面弦撩。如果這個(gè)連接是空閑等待的狀態(tài)的話,那就在idleConns里面加一個(gè)自己的指針论皆!

其實(shí)歸還的過(guò)程益楼,就是檢查一下我打算還的這個(gè)連接猾漫,是不是超售的產(chǎn)物,如果是就沒(méi)必要池化了感凤,直接刪除就可以了悯周。不是的話,就是把連接自身(一個(gè)指針)在idleConns也append一下陪竿。

等等禽翼,上面的邏輯似乎有點(diǎn)不對(duì)?我們來(lái)理一下獲取連接流程:

先waitTurn族跛,拿到令牌闰挡。而令牌數(shù)量是根據(jù)pool里面的queue決定的。

拿到令牌了礁哄,去庫(kù)房idleConns里面拿空閑的連接长酗。沒(méi)有的話就自己newConn一個(gè),并且把他記錄到conns里面桐绒。

用完了夺脾,就調(diào)用put歸還:也就是從conns添加這個(gè)連接的指針到idleConns。歸還的時(shí)候就檢查在newConn時(shí)候是不是已經(jīng)做了超賣(mài)標(biāo)記了茉继。是的話就不轉(zhuǎn)移到idleConns咧叭。

我當(dāng)時(shí)疑惑了好久,既然始終都需要獲得令牌才能得到連接烁竭,令牌數(shù)量是定的佳簸。為什么還會(huì)超賣(mài)呢?翻了一下源碼颖变,我的答案是:

雖然Get方法獲取連接是newConn這個(gè)私用方法生均,受到令牌管制導(dǎo)致不會(huì)出現(xiàn)超賣(mài)。但是這個(gè)方法接受傳參:pooled bool腥刹。所以我猜是擔(dān)心其他人調(diào)用這個(gè)方法時(shí)候马胧,不管三七二十一就傳了true,導(dǎo)致poolSize越來(lái)越大衔峰。

總的來(lái)說(shuō)佩脊,redis這個(gè)連接池的連接數(shù)控制,還是在queue這個(gè)我稱(chēng)為令牌的chan進(jìn)行操作垫卤。


總結(jié)

上面可以看到威彰,連接池的最基本的保證,就是獲取連接時(shí)候的線程安全穴肘。但是在實(shí)現(xiàn)諸多額外特性時(shí)候卻又從不同角度來(lái)實(shí)現(xiàn)歇盼。還是非常有意思的。但是不管存儲(chǔ)結(jié)構(gòu)是用chan還是還是slice评抚,都可以很好的實(shí)現(xiàn)這一點(diǎn)豹缀。如果像sql或者redis那樣用slice來(lái)存儲(chǔ)連接伯复,就得維護(hù)一個(gè)結(jié)構(gòu)來(lái)表示排隊(duì)等候的效果。

以上內(nèi)容都是我自己的一些感想邢笙,分享出來(lái)歡迎大家指正啸如,順便求一波關(guān)注,有想法的伙伴可以評(píng)論或者私信我哦~


作者:Xiao淩求個(gè)好運(yùn)氣

鏈接:https://juejin.im/post/5e58e3b7f265da57537eb7ed

來(lái)源:掘金

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末氮惯,一起剝皮案震驚了整個(gè)濱河市叮雳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌妇汗,老刑警劉巖债鸡,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異铛纬,居然都是意外死亡厌均,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)告唆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)棺弊,“玉大人,你說(shuō)我怎么就攤上這事擒悬∧K” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵懂牧,是天一觀的道長(zhǎng)侈净。 經(jīng)常有香客問(wèn)我,道長(zhǎng)僧凤,這世上最難降的妖魔是什么畜侦? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮躯保,結(jié)果婚禮上旋膳,老公的妹妹穿的比我還像新娘。我一直安慰自己途事,他們只是感情好验懊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著尸变,像睡著了一般义图。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上召烂,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天碱工,我揣著相機(jī)與錄音,去河邊找鬼。 笑死痛垛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的桶蛔。 我是一名探鬼主播匙头,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼仔雷!你這毒婦竟也來(lái)了蹂析?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤碟婆,失蹤者是張志新(化名)和其女友劉穎电抚,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體竖共,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蝙叛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了公给。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片借帘。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖淌铐,靈堂內(nèi)的尸體忽然破棺而出肺然,到底是詐尸還是另有隱情,我是刑警寧澤腿准,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布际起,位于F島的核電站,受9級(jí)特大地震影響吐葱,放射性物質(zhì)發(fā)生泄漏街望。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一弟跑、第九天 我趴在偏房一處隱蔽的房頂上張望它匕。 院中可真熱鬧,春花似錦窖认、人聲如沸豫柬。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)烧给。三九已至,卻和暖如春喝噪,著一層夾襖步出監(jiān)牢的瞬間础嫡,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留榴鼎,地道東北人伯诬。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像巫财,于是被迫代替她去往敵國(guó)和親盗似。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 談到docker源碼平项,其實(shí)網(wǎng)上有很多的源碼的分析的文章赫舒,也看過(guò)一些大牛寫(xiě)的docker源碼解讀的文章,收獲很大闽瓢。我...
    跨界師閱讀 1,328評(píng)論 2 3
  • ``` /* * @Descripttion: golang 連接mysql demo * @version: *...
    weilin_jin閱讀 295評(píng)論 0 0
  • 1 go語(yǔ)言的RPC機(jī)制 RPC(Remote Procedure Call接癌,遠(yuǎn)程過(guò)程調(diào)用)是一種通過(guò)網(wǎng)絡(luò)從遠(yuǎn)程計(jì)...
    中v中閱讀 2,752評(píng)論 0 1
  • 某次做checklist,當(dāng)看到機(jī)器的監(jiān)控時(shí)扣讼,發(fā)現(xiàn)下午某時(shí)刻某臺(tái)機(jī)器突發(fā)大量CLOSE_WAIT缺猛。一下子就懷疑是服...
    imnx閱讀 773評(píng)論 0 5
  • 一、基礎(chǔ)知識(shí) Redis是一個(gè)開(kāi)源的椭符、使用C語(yǔ)言編寫(xiě)的枯夜、支持網(wǎng)絡(luò)交互的、可基于內(nèi)存也可持久化的Key-Value數(shù)...
    不屈真實(shí)閱讀 946評(píng)論 0 4