此專案改寫根據個人習慣來建立,實際專案準備工作不分先後順序,準備好了就行,不需要糾結這個。
本專案屬於公司內部專案,只是提供思路和關鍵程式碼,需要的原始碼可以新增本人微信: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)
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 協議》,轉載必須註明作者和本文連結