国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

腳本之家,腳本語(yǔ)言編程技術(shù)及教程分享平臺(tái)!
分類導(dǎo)航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務(wù)器之家 - 腳本之家 - Golang - Golang連接池的幾種實(shí)現(xiàn)案例小結(jié)

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

2020-06-06 12:22Xiao淩求個(gè)好運(yùn)氣 Golang

這篇文章主要介紹了Golang連接池的幾種實(shí)現(xiàn)案例小結(jié),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

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

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

 

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

 

?
1
2
3
4
5
6
type Pool struct {
 m sync.Mutex // 保證多個(gè)goroutine訪問時(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)單:

?
1
2
3
4
5
6
7
8
9
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就可以保證線程安全:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//從資源池里獲取一個(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里面獲取,如果沒有的話我們就利用我們?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)閉連接池的呢?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//關(guān)閉資源池,釋放資源
func (p *Pool) Close() {
 p.m.Lock()
 defer p.m.Unlock()
 
 if p.closed {
 return
 }
 
 p.closed = true
 
 //關(guān)閉通道,不讓寫入了
 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)行讀寫。需要先把這個(gè)標(biāo)志位設(shè)定后,關(guān)閉res這個(gè)chan,使得Acquire方法無(wú)法再獲取新的連接。我們?cè)賹?duì)res這個(gè)chan里面的連接進(jìn)行Close操作。

釋放連接

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (p *Pool) Release(r io.Closer){
 //保證該操作和Close方法的操作是安全的
 p.m.Lock()
 defer p.m.Unlock()
 
 //資源池都關(guān)閉了,就省這一個(gè)沒有釋放的資源了,釋放即可
 if p.closed {
 r.Close()
 return
 }
 
 select {
 case p.res <- r:
 log.Println("資源釋放到池子里了")
 default:
 log.Println("資源池滿了,釋放這個(gè)資源吧")
 r.Close()
 }
}

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

  1. 我們對(duì)連接最大的數(shù)量沒有限制,如果線程池空的話都我們默認(rèn)就直接新建一個(gè)連接返回了。一旦并發(fā)量高的話將會(huì)不斷新建連接,很容易(尤其是MySQL)造成too many connections的報(bào)錯(cuò)發(fā)生。
  2. 既然我們需要保證最大可獲取連接數(shù)量,那么我們就不希望數(shù)量定的太死。希望空閑的時(shí)候可以維護(hù)一定的空閑連接數(shù)量idleNum,但是又希望我們能限制最大可獲取連接數(shù)量maxNum。
  3. 第一種情況是并發(fā)過多的情況,那么如果并發(fā)量過少呢?現(xiàn)在我們?cè)谛陆ㄒ粋€(gè)連接并且歸還后,我們很長(zhǎng)一段時(shí)間不再使用這個(gè)連接。那么這個(gè)連接很有可能在幾個(gè)小時(shí)甚至更長(zhǎng)時(shí)間之前就已經(jīng)建立的了。長(zhǎng)時(shí)間閑置的連接我們并沒有辦法保證它的可用性。便有可能我們下次獲取的連接是已經(jīng)失效的連接。

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

 

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

 

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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ù)并沒有新建數(shù)據(jù)庫(kù)連接。而新建連接在哪個(gè)函數(shù)呢?我們可以在Query方法一路往回找,我們可以看到這個(gè)函數(shù):func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn, error)。而我們從連接池獲取連接的方法,就從這里開始:

獲取連接

?
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// 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)過期。
 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)求)并且開始等待
 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)過期了等等。
 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è)類似排隊(duì)機(jī)制的connRequests來(lái)解決獲取等待連接的過程。同時(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)那幾行:

?
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
...
 // 如果已經(jīng)超過最大打開數(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 {
  // 既然沒人排隊(duì),就看看到了最大連接數(shù)目沒有。沒到就歸還給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ō)的小問題了。不會(huì)出現(xiàn)連接太多導(dǎo)致無(wú)法控制too many connections的情況。也很好了維持了連接池的最小數(shù)量。同時(shí)也做了相關(guān)對(duì)于連接健康性的檢查操作。
值得注意的是,作為標(biāo)準(zhǔn)庫(kù)的代碼,相關(guān)注釋和代碼都非常完美,真的可以看的神清氣爽。

 

redis Golang實(shí)現(xiàn)的Redis客戶端

 

這個(gè)Golang實(shí)現(xiàn)的Redis客戶端,是怎么實(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)如下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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,那么問題來(lái)了:

到底連接存在哪里?

新建連接池連接

我們先從新建連接池連接開始看:

?
1
2
3
4
5
6
7
8
9
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ì)往連接池填滿空閑的連接。
  2. go p.reaper(opt.IdleCheckFrequency)則會(huì)在初始化連接池的時(shí)候就會(huì)起一個(gè)go程,周期性的淘汰連接池里面要被淘汰的連接。

獲取連接

?
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
func (p *ConnPool) Get(ctx context.Context) (*Conn, error) {
 if p.closed() {
 return nil, ErrClosed
 }
 
 //這邊和前面sql獲取連接函數(shù)的流程先不同。sql是先看看連接池有沒有空閑連接,有的話先獲取不到再排隊(duì)。這邊是直接先排隊(duì)獲取令牌,排隊(duì)函數(shù)后面會(huì)分析。
 err := p.waitTurn(ctx)
 if err != nil {
 return nil, err
 }
 //前面沒出error的話,就已經(jīng)排隊(duì)輪候到了。接下來(lái)就是獲取的流程。
 for {
 p.connsMu.Lock()
  //從空閑連接里面先獲取一個(gè)空閑連接。
 cn := p.popIdle()
 p.connsMu.Unlock()
 
 if cn == nil {
   // 沒有空閑連接時(shí)候直接跳出循環(huán)。
 break
 }
 // 判斷是否已經(jīng)過時(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)
 
 // 如果沒有空閑連接的話,這邊就直接新建連接了。
 newcn, err := p.newConn(ctx, true)
 if err != nil {
  // 歸還令牌。
 p.freeTurn()
 return nil, err
 }
 
 return newcn, nil
}

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

  1. sql的排隊(duì)意味著我對(duì)連接池申請(qǐng)連接后,把自己的編號(hào)告訴連接池。連接那邊一看到有空閑了,就叫我的號(hào)。我答應(yīng)了一聲,然后連接池就直接給個(gè)連接給我。我如果不歸還,連接池就一直不叫下一個(gè)號(hào)。
  2. redis這邊的意思是,我去和連接池申請(qǐng)的不是連接而是令牌。我就一直排隊(duì)等著,連接池給我令牌了,我才去倉(cāng)庫(kù)里面找空閑連接或者自己新建一個(gè)連接。用完了連接除了歸還連接外,還得歸還令牌。當(dāng)然了,如果我自己新建連接出錯(cuò)了,我哪怕拿不到連接回家,我也得把令牌給回連接池,不然連接池的令牌數(shù)少了,最大連接數(shù)也會(huì)變小。

而:

?
1
2
3
4
5
6
7
8
9
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ù):

新建連接

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 {
 // 如果連接池滿了,會(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í)先看池子滿了沒有。滿的話后面歸還的時(shí)候會(huì)標(biāo)記,后面會(huì)刪除。那么這個(gè)后面會(huì)刪除,指的是什么時(shí)候呢?那就是下面說(shuō)的歸還連接的時(shí)候了。

歸還連接

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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)記過不要入池的,這邊就刪除了。當(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í)歸還的過程,就是檢查一下我打算還的這個(gè)連接,是不是超售的產(chǎn)物,如果是就沒必要池化了,直接刪除就可以了。不是的話,就是把連接自身(一個(gè)指針)在idleConns也append一下。

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

  1. 先waitTurn,拿到令牌。而令牌數(shù)量是根據(jù)pool里面的queue決定的。
  2. 拿到令牌了,去庫(kù)房idleConns里面拿空閑的連接。沒有的話就自己newConn一個(gè),并且把他記錄到conns里面。
  3. 用完了,就調(diào)用put歸還:也就是從conns添加這個(gè)連接的指針到idleConns。歸還的時(shí)候就檢查在newConn時(shí)候是不是已經(jīng)做了超賣標(biāo)記了。是的話就不轉(zhuǎn)移到idleConns。

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

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

總的來(lái)說(shuō),redis這個(gè)連接池的連接數(shù)控制,還是在queue這個(gè)我稱為令牌的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ì)等候的效果。

到此這篇關(guān)于Golang連接池的幾種實(shí)現(xiàn)案例小結(jié)的文章就介紹到這了,更多相關(guān)Golang 連接池內(nèi)容請(qǐng)搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!

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

延伸 · 閱讀

精彩推薦
  • Golanggo語(yǔ)言制作端口掃描器

    go語(yǔ)言制作端口掃描器

    本文給大家分享的是使用go語(yǔ)言編寫的TCP端口掃描器,可以選擇IP范圍,掃描的端口,以及多線程,有需要的小伙伴可以參考下。 ...

    腳本之家3642020-04-25
  • GolangGolang通脈之?dāng)?shù)據(jù)類型詳情

    Golang通脈之?dāng)?shù)據(jù)類型詳情

    這篇文章主要介紹了Golang通脈之?dāng)?shù)據(jù)類型,在編程語(yǔ)言中標(biāo)識(shí)符就是定義的具有某種意義的詞,比如變量名、常量名、函數(shù)名等等,Go語(yǔ)言中標(biāo)識(shí)符允許由...

    4272021-11-24
  • Golanggolang的httpserver優(yōu)雅重啟方法詳解

    golang的httpserver優(yōu)雅重啟方法詳解

    這篇文章主要給大家介紹了關(guān)于golang的httpserver優(yōu)雅重啟的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,...

    helight2992020-05-14
  • Golanggolang 通過ssh代理連接mysql的操作

    golang 通過ssh代理連接mysql的操作

    這篇文章主要介紹了golang 通過ssh代理連接mysql的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧...

    a165861639710342021-03-08
  • Golanggolang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法

    golang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法

    今天小編就為大家分享一篇golang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧 ...

    李浩的life12792020-05-27
  • Golanggolang如何使用struct的tag屬性的詳細(xì)介紹

    golang如何使用struct的tag屬性的詳細(xì)介紹

    這篇文章主要介紹了golang如何使用struct的tag屬性的詳細(xì)介紹,從例子說(shuō)起,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看...

    Go語(yǔ)言中文網(wǎng)11352020-05-21
  • GolangGolang中Bit數(shù)組的實(shí)現(xiàn)方式

    Golang中Bit數(shù)組的實(shí)現(xiàn)方式

    這篇文章主要介紹了Golang中Bit數(shù)組的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧...

    天易獨(dú)尊11682021-06-09
  • Golanggo日志系統(tǒng)logrus顯示文件和行號(hào)的操作

    go日志系統(tǒng)logrus顯示文件和行號(hào)的操作

    這篇文章主要介紹了go日志系統(tǒng)logrus顯示文件和行號(hào)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧...

    SmallQinYan12302021-02-02
主站蜘蛛池模板: 综合久久av | 久久伊人亚洲 | 国产伦精品一区二区三区四区视频 | 日韩欧美网站 | 久久久高清 | 亚洲国产精品一区二区久久 | a毛片视频| 亚洲精品成人 | 欧美日韩精品一区二区三区蜜桃 | 欧美国产精品一区二区 | 欧美午夜精品久久久久久浪潮 | 好吊在线观看 | 韩国精品一区 | 福利网在线 | 黄久久久 | 一级毛片视频 | 亚洲天堂电影 | 久久精品国产精品青草 | 亚洲国产精品成人精品 | 精品欧美乱码久久久久久1区2区 | 成人短视频在线 | 日韩中文字幕在线播放 | 欧美一区二区三区在线观看视频 | 依人网站 | 亚洲国产成人精品女人久久久 | 欧美成人精品高清视频在线观看 | 91在线视频观看 | 国产精品久久久久久久天堂 | 日韩综合一区 | 天堂资源最新在线 | 亚洲一区二区在线播放 | 欧美成人一区二免费视频软件 | 色橹橹欧美在线观看视频高清 | 自拍偷拍欧美 | 久久伊99综合婷婷久久伊 | 亚洲一区二区三区 | 中文字幕国产视频 | 欧美成年人网站 | 日韩一区二区三区精品 | 九九热精品在线 | 中文字幕一区二区三区四区不卡 |