Go實戰準備工作---建立資料庫連線池

棋佈發表於2020-10-07
此專案改寫根據個人習慣來建立,實際專案準備工作不分先後順序,準備好了就行,不需要糾結這個。

本專案屬於公司內部專案,只是提供思路和關鍵程式碼,需要的原始碼可以新增本人微信:wjw1103608332
專案屬於智慧客服平臺的以及智慧外呼子平臺的專案,改寫的只是PHP部分的程式碼,此專案是結合了Go和NLP以及Java共同實現的專案。PHP是負責web端和go端的資料互動,以及業務處理。本次改寫除了基本的業務替換,還有效能的對比以及優化,沒有這些改寫也就毫無意義。此前PHP的版本是ThinkPHP3.2,相當古老,本次也是根據公司的需求按照實際情況來改寫,不僅僅個人的Demo,後期的變動也是有可能的,由於領導的瞎指揮,不一定能盡人意。

話不多說,目前需要準備工作有:資料庫的連線池、redis連線池、go協程連線池、日誌管理等。內容可能比較多,今天這篇就介紹資料庫連線池,其他兩個後面文章會補上。目前網路請求框架是Gin和Beengo,還沒有決定使用哪個,暫時用的是Gin,可擴充套件性強,靈活使用。後續對比再來確定使用哪個,畢竟框架只有最合適沒有最好一說,後續決定使用 哪個也會說明原因,也許是技術原因,也許是客觀原因。

本專案屬於雲服務智慧機器人專案,屬於SAAS服務專案,總庫和企業分庫的方式,由於庫已經建立並使用,無法進行更改和替換測試,所以資料庫的設計這一塊我們只有優化語句,對於結構不做過多描述,當然吐槽是少不了的,畢竟,有時候寫程式碼碰到不合理的結構時是真的很氣人。
 本專案會 涉及到多個資料庫的切換,正常來說是兩個庫,一個總庫,一個分庫,無論哪個企業,最多也就兩個庫的切換。當然,如果是後臺管理員賬號會涉及到所有企業的庫切換。

單庫連線池建立

  第一步:引入資料庫驅動: _ "github.com/go-sql-driver/mysql"   也可以是其他驅動,這沒什麼好解釋的,用的最多的,也是目前最廣泛使用的。建立程式碼如下:
//建立指定庫的連線池
func createNewDBConn(dbName string) *sql.DB {
    var err error
    dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?timeout=30s&charset=utf8mb4&collation=utf8mb4_general_ci", config.Instance.MySqlUser, config.Instance.MySqlPwd, config.Instance.MySqlAddress, dbName)
    fmt.Println("dsn: ", dsn)
    db, err = sql.Open("mysql", dsn)
    if err != nil {
        return nil
    }
    db.SetMaxOpenConns(100)
    db.SetMaxIdleConns(50)
    db.SetConnMaxLifetime(time.Minute * 10) //10分鐘回收
    err = db.Ping()
    if err != nil {
        return nil
    }
    return db
}
  直接設定資料庫名稱或者寫死就是單庫的連線池。

   思考幾個問題:1、最大連線數和最大空閒數怎麼設定和確定?2、回收時間如何確定呢?

   這兩個問題對於只會PHP開發的同學來說確實有點難度,因為如果沒用過swoole或者基於swoole框架的話,是沒有池的概念的。更別談資料最大值的設定。但是對於Java(我們Android也是哈)或者其他使用常駐記憶體的開發者來說,這是非常常見的問題,最原始的莫過於執行緒池的使用和核心執行緒數優化了,想想就頭痛。資料庫的其實沒那麼複雜。我們設定成100,是因為MySQL預設連線數就是100,當我們的機器承受不了的時候,我們需要執行命令:show processlist 檢視下當前執行的query數量,然後使用TOP命令觀察佔用情況。當然,機器牛逼的可以直接設定成更多的,比如200,那就執行命令:set GLOBAL max_connections=200或者這更多,檢視佔用情況。找到最合適的資料,這就是簡單的優化方案,複雜一點的可以藉助壓測工具進行壓測,看看機器承受的臨界值是多少再來確定具體的值。空閒值我的做法是根據使用者量和訪問情況來設定的。其實10個就完全滿足我們目前的需求,為了顯示SAAS服務的高階大氣,就寫成50,也好方便商務出去吹牛逼。空閒時間同樣是根據業務實際情況,高併發狀態下長點是可以接受的,太短同樣會引起頻繁的建立,太長也會 佔用資源得不到有效的釋放。

多庫切換連線池建立

  鑑於沒有想到好的辦法,目前的做法是建立多個資料庫把資料庫放在map集合裡面,再從map裡面去拿,有就直接使用沒有就建立。首先建立結構體
type DbPool struct {
    maxDbs int
    DBs    map[string]*sql.DB
    mux    *sync.RWMutex
}
設定一個建立所有池的大小,注意,初始時2個,所以,預設就2個大小的切片。
func newDbPool(maxDBs int) *DbPool {
    return &DbPool{maxDbs: maxDBs, DBs: make(map[string]*sql.DB, 2), mux: new(sync.RWMutex)}
}
設定一個呼叫庫的函式,有就呼叫,沒有建立。
func (dp *DbPool) GetDB(dbName string) *sql.DB {
    dp.mux.Lock()
    defer dp.mux.Unlock()
    if db, ok := dp.DBs[dbName]; ok {
        return db
    }
    if len(dp.DBs) > dp.maxDbs {
        for k, v := range dp.DBs {
            _ = v.Close()
            delete(dp.DBs, k)
            break
        }
    }
    //獲取連線池的時候,如果此資料庫的連線池不存在,建立新的連線池
    if newDB := createNewDBConn(dbName); newDB != nil {
        dp.DBs[dbName] = newDB
        return newDB
    }

    return nil
你以為就完了?不,建立池,這種全域性使用的,必定使用yaml建立常量。常量可以直接const,也可以yaml設定配置檔案,官方描述的區別是:const修改要重新發布,yaml更改可以不釋出版本,直接修改立即生效,對於運維的同學是個福音。當然,也要看你打包的方式了。
  依然是使用庫:gopkg.in/yaml.v2   沒錯,還是第三方的框架,沒有這些框架,我們就沒必要換Go了,什麼都要自己實現這也太痛苦了。接下來設定結構體

type Configuration struct {
    MySqlPwd     string `yaml:"mySqlPwd"`
    MySqlUser    string `yaml:"mySqlUser"`
    MySqlAddress string `yaml:"mySqlAddress"`
    MysqlMaxDBs  int    `yaml:"mysqlMaxDBs"`
}
 設定全域性變數,既然是配置檔案,少不了單例模式了:
var (
    once     sync.Once
    Instance *Configuration
)
載入並解析yaml檔案
func InitConfig() {
    config, err := ioutil.ReadFile("config/config.yaml")
    if err != nil {
        log.Println("configYaml get err:", err)
    }
    err = yaml.Unmarshal(config, GetConfig())
    if err != nil {
        log.Println("configYaml get err:", err)
    }
Go的單例模式不像Java的那麼簡單粗暴,懶漢式、餓漢式、靜態內部類之類的。
func GetConfig() *Configuration {
    once.Do(func() {
        Instance = &Configuration{}
    })
    return Instance
}
我這邊採用的是once.Do的方式來實現單例,還有其他的方式,大家都可以嘗試,利弊目前不清楚,待確定。



總結:本篇部落格記錄的是單庫和多庫的連線池建立,對於多庫採用的是放切片統一管理,但是缺點是:對於admin管理多個資料庫切換是會出現爆發式的池建立過程,好在只有一個賬號,不會出現大量場景。有更好的方式,請不吝賜教!同時,對於資料庫配置的常量,我這邊採用的yaml檔案配置,並且採用單例的模式獲取。(既然是成長,就是沒難度也要給自己增加難度上)

ps:我們專案還有使用pgsql的部分業務,但是連線池都是一樣的,pgsql目前是單庫的連線,所以比較簡單就沒有加上,後續程式碼可能會出現這種連線。其他的幾種連線池,後續文章分享記錄。
本作品採用《CC 協議》,轉載必須註明作者和本文連結
生命有限,隨心而活,寫程式碼不香嗎?

相關文章