版本
golang -- 1.12.4
mongodb -- 4.0
go driver -- 1.0.0簡介
在上一篇《用golang實(shí)現(xiàn)mongodb數(shù)據(jù)庫連接池-基本篇》我們實(shí)現(xiàn)了mongodb的golang driver按序使用的基本版瓶埋,但還需要進(jìn)一步提升效率和高并發(fā)安全昔头。本篇張實(shí)現(xiàn)高效率協(xié)程安全版擎值。
- data race
什么是data race袋哼,考慮如下代碼:
var balance int
func Deposit(amount int){ balance = balance + amount}
func Balance() int { return balance}
//Alice:
go func(){
bank.Deposit(200) // A1
fmt.Println("=", bank.Balance()) // A2
}()
//Bob
go bank.Deposit(100) // B
當(dāng)alice和bob同時(shí)執(zhí)行如上的操作,最后的存款有幾種可能性刚夺?
根據(jù)直覺會(huì)有3種可能:
alice first | bob first | alice/bob/alice |
---|---|---|
0 | 0 | 0 |
A1 200 | B 100 | A1 200 |
A2 "=200" | A1 300 | B 300 |
B 300 | A2 "=300" | A2 "=300" |
這個(gè)結(jié)果最后存款都是剩余300似乎也沒什么問題献丑,但是這里還有第4種可能,那就是bob的存款操作發(fā)生在A1的balance + amount之后侠姑,但是在A1的balance =之前创橄,那么會(huì)出現(xiàn)什么?
Data | race
:-: | :-: | :-
| 0 |
A1r | 0 | ...=balance + amount
B | 100 |
A1w | 200 | balace = ...
A2 | "=200" |
現(xiàn)在Alice賬戶剩余200莽红,在data race中100被程序沖掉妥畏。
設(shè)計(jì)
我們使用mutual exclusion的方式進(jìn)行協(xié)程安全設(shè)計(jì),具體請(qǐng)參見《golang實(shí)現(xiàn)協(xié)程安全的幾種方式》核心代碼
const(
MAX_CONNECTION = 10
INITIAL_CONNECTION = 4
AVAILABLE = false
USED = true
)
/*
代碼取了一個(gè)巧安吁,用實(shí)際存放數(shù)據(jù)庫指針的大小ClientPool.size和mongodata.flag來表示上述a醉蚁,b兩個(gè)狀態(tài)
如果mongodata.flag都為USED,那么需要新申請(qǐng)個(gè)數(shù)據(jù)庫連接: size++
clientList: the client pool
clientAvailable: the available flag, means the location and available flag in the client pool
size: the size of allocated client pool <= MAX_CONNECTION
*/
type mongodata struct{
client *mongo.Client
pos int
flag bool
}
type ClientPool struct{
clientList [MAX_CONNECTION]mongodata
size int
}
//create a new database connection to the pool
func (cp *ClientPool) allocateCToPool(pos int) (err error){
cp.clientList[pos].client, err = Dbconnect()
if err != nil {
utils.Logger.SetPrefix("WARNING ")
utils.Logger.Println("allocateCToPool - allocateCToPool failed,position: ", pos, err)
return err
}
cp.clientList[pos].flag = USED
cp.clientList[pos].pos = pos
return nil
}
//apply a connection from the pool
func (cp *ClientPool) getCToPool(pos int){
cp.clientList[pos].flag = USED
}
//free a connection back to the pool
func (cp *ClientPool) putCBackPool(pos int){
cp.clientList[pos].flag = AVAILABLE
}
//program apply a database connection
func GetClient() (mongoclient *mongodata, err error) {
mu.RLock()
for i:=1; i<cp.size; i++ {
if cp.clientList[i].flag == AVAILABLE{
return &cp.clientList[i], nil
}
}
mu.RUnlock()
mu.Lock()
defer mu.Unlock()
if cp.size < MAX_CONNECTION{
err = cp.allocateCToPool(cp.size)
if err != nil {
utils.Logger.SetPrefix("WARNING ")
utils.Logger.Println("GetClient - DB pooling allocate failed", err)
return nil, err
}
pos := cp.size
cp.size++
return &cp.clientList[pos], nil
} else {
utils.Logger.SetPrefix("WARNING ")
utils.Logger.Println("GetClient - DB pooling is fulled")
return nil, errors.New("DB pooling is fulled")
}
}
//program release a connection
func ReleaseClient(mongoclient *mongodata){
mu.Lock()
cp.putCBackPool(mongoclient.pos)
mu.Unlock()
}
這樣我們就完成了一個(gè)高效率協(xié)程安全的設(shè)計(jì)鬼店,完整代碼地址: https://github.com/kmnemon/golang-mongodb-pool