Golang DB 連線池用法分析

Golang DB 連線池用法分析

go連線mysql為什麼需要 import _ "github.com/go-sql-driver/mysql"

go中import _的作用只執行引入包的init函式,那麼go-sql-driver/mysql 的init函式又做了什麼,在

database/sql

中的

drivers map[string]driver。Driver

註冊引擎 mysql => MySQLDriver{}

// go-sql-driver/mysql/driver。gofunc init() { sql。Register(“mysql”, &MySQLDriver{})}

SetMaxIdleConns/SetMaxOpenConns/SetConnMaxLifetime 設定的值有什麼用呢?

db。maxLifetime 連線從建立開始存活的時間,mysql預設tcp連線的超時時間 8h

db。maxOpen 開啟的連線最大數量,超過該數量後,query會被阻塞等待可用連線

db。maxIdle 空閒池維持的最大連線數量

sql.Open為什麼只需要一次呼叫即可?

載入驅動程式 go-sql-driver/mysql

初始化DB資料結構

構造建立連線channel/重置連線channel

這裡並有實際去和資料建立連線,也沒有對資料庫連線的引數校驗,只是初始化了DB結構體,DB結構體中已經包含了連線池 freeConn []*driverConn, 沒有必要多次呼叫open,全域性維護一個DB即可,如果需要驗證 賬戶密碼/網路是否能通訊,需要呼叫ping來檢測。

type DB struct { waitDuration int64 // 統計每次連結的等待時間 connector driver。Connector // 已經關閉的連線數量 numClosed uint64 mu sync。Mutex freeConn     []*driverConn               // 空閒池 connRequests map[uint64]chan connRequest // 等待連線佇列,當前連線達到maxOpen時,就無法不在建立連線,建立一個請求的channel入佇列並等待並阻塞當前goroutine nextRequest  uint64                      // connRequests[] 指向下一個坑位 numOpen      int                         // 正在使用的連線數量 // channel 透過此channel來接受建立新的session連線 // DB。connectionOpener for select 接收channel openerCh          chan struct{} resetterCh        chan *driverConn       // 重置session以便其他query複用當前session closed            bool                   // 標記是否已經關閉連結 dep               map[finalCloser]depSet // lastPut           map[*driverConn]string // 除錯用 maxIdle           int                    // 維持的最大空閒連線數,小於等於0使用defaultMaxIdleConns(2) maxOpen           int                    // 維持的最大連線數,0無限制 maxLifetime       time。Duration          // 連線可以重用的最長時間 cleanerCh         chan struct{} waitCount         int64 // 等待連線的總數 maxIdleClosed     int64 // 由於空閒而連線的總數 maxLifetimeClosed int64 // 連線存活時間超過maxLifetime而關閉的時間 stop func() // 取消接收 建立連線channel(openerCh)/重置session channel(resetterCh)}

如果獲取資料連線池的狀態?當前的連線總數,SetMaxOpenConns/SetMaxIdleConns的數量是否合適。

使用db。Stats可以檢視當前連線池的一些狀態,這邊返回了一個DBStats結構體,一起看下:

type DBStats struct { MaxOpenConnections int // 開啟的最大連線數,包含已經關閉了的連線 // 連線池狀態 OpenConnections int // 當前建立連線的數量,包括正在使用和空閒的數量 InUse           int // 正在使用的連線數 Idle            int // 已建立連線,空閒中的連線數 // 統計 WaitCount         int64         // 等待連線的總數,這裡需要著重關注一下 WaitDuration      time。Duration // query持續等待連線的總時長 MaxIdleClosed     int64         // 達到SetMaxIdleConns,而關閉的連線數量 MaxLifetimeClosed int64         // 達到SetConnMaxLifetime。 而關閉的連線數量}

WaitCount 著重關注一下,看下是不是有慢查詢阻塞了可用的連線數量

MaxIdleConns設定的過小,而請求數過多導致,會導致MaxIdleClosed比較大,WaitCount也會比較大

注意:db。Stats不要呼叫過於頻繁,它會對整個DB連線池加鎖,過於頻繁有一定開銷。

DB.Ping()做了哪些事情?DB是如何從連線池中獲取一個可用的連線的?

獲取連線,可以複用連線 (cachedOrNewConn)

獲取一個可用的連線 driverConn複用空閒池中freeConn已有的連線從空閒池中移除第一個連線conn這期間都是有鎖的,freeConn是一個切片,是併發不安全的該連線是否在生命週期內 lifetime —— db。SetLifeTime設定的tcp連線存活時間本連線過了生命週期,返回 driver。ErrBadConn返回此conndb。SetMaxOpenConns設定了最大開啟的連線數,且當前開啟的連線已經達到最大數建立一個等待請求,放入等待佇列,阻塞當前goroutine等待超時使用context取消,或者等待直到獲取可用的連線ctx取消後還是獲取到了連線,放回空閒池獲取到可用連線,統計本次阻塞時長,可以注意到如果DB。Stats()。WaitDuration大了以後問題就很嚴重瞭如果本連線過了生命週期,返回 driver。ErrBadConn返回此Conn封裝driver返回的connect到driverConn標記driverConn inUse使用中記錄連線建立時間createdAtdb指向連線池真正底層的連線

原始碼小細節:

移除切片第一個元素:

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

刪除map中的元素: delete(db。connRequests, reqKey)

map[key]interface: 也可以使用channel作為key

生命週期判斷:createdAt。Add(lifetime)。Before(time。now())

參觀一下context的用法

select {// 這裡留取消的口case <-ctx。Done():  select {  // 之前我們分析過select儘量不要加default,單那是for select結構,會造成自旋鎖,長期佔用M不釋放  // 如果這裡不用default它就阻塞一直等待req channel中有connect,這裡並不是為了等待,只是為了清理一下channel的connect,防止孤兒connect  default: case ret, ok := <-req: //。。。 }  return nil, ctx。Err()case ret, ok := <-req:  // 。。。  return ret。conn, ret。err}// 我們在外面如何控制超時呢?fun bar(){  t := time。After(10)  ctx := context。Background()  res := make(chan struct{})  go func() {    ci, _ := conn(ctx)    res<-ci }()  select {  case <-t:    ctx。Done()  case ci := <-res:    if ci != nil {   } }}