本人最近用go写业务后台,数据库是mysql,用的是gorm连接,一般而言,是数据库先启动,再启动业务后台服务。
本人在程序启动的时候,会事先进行数据库连接。
下面是我发现的现象:
1.数据库先启动,再启动业务后台,业务操作正常,此时断掉数据库,业务操作异常(数据库操作报错), 此时启动数据库,再进行业务请求,业务操作正常(数据库请求正常)。
2.业务后台先启动,数据库后启动,此时业务操作异常(数据库操作报错)
很明显,现象1是我想要的结果,断掉数据库后再重新启动数据库,gorm内部给人感觉会自动重连一样。
但是现象2很明显不是想要的效果,没有自动重连的感觉。
本人将调试时发现的情况给予说明
如下所示,是打开数据库的gorm调用,出问题的情况下,我的代码里面只会调用一次。
func Open(dialect string, args ...interface{}) (db *DB, err error) {
if len(args) == 0 {
err = errors.New("invalid database source")
return nil, err
}
var source string
var dbSQL SQLCommon
var ownDbSQL bool
switch value := args[0].(type) {
case string:
var driver = dialect
if len(args) == 1 {
source = value
} else if len(args) >= 2 {
driver = value
source = args[1].(string)
}
dbSQL, err = sql.Open(driver, source)
ownDbSQL = true
case SQLCommon:
dbSQL = value
ownDbSQL = false
default:
return nil, fmt.Errorf("invalid database source: %v is not a valid type", value)
}
db = &DB{
db: dbSQL,
logger: defaultLogger,
callbacks: DefaultCallback,
dialect: newDialect(dialect, dbSQL),
}
db.parent = db
if err != nil {
return
}
// Send a ping to make sure the database connection is alive.
if d, ok := dbSQL.(*sql.DB); ok {
if err = d.Ping(); err != nil && ownDbSQL {
d.Close()
}
}
return
}
当数据库先启动时,业务程序后启动时,代码走到d, ok := dbSQL.(*sql.DB),sql.DB类型的变量正常,里面的closed变量为false,代表打开状态。
后面就算关闭掉数据库,该sql.DB里面的closed依然为false,代表数据库是打开的。
当数据库后启动时,业务程序先启动时,代码走到d, ok := dbSQL.(*sql.DB),sql.DB类型的变量异常,会进入到d.Close()逻辑中去,此时DB里面的closed变量为true,代表关闭状态。
后面就算关闭掉数据库,该sql.DB里面的closed依然为true,代表数据库是关闭的。
而下面database/sql/sql.go里面的一段代码代表,如果数据库是打开的,若没有正常可用的连接,依然会创建连接;如果数据库是关闭的,则直接返回错误。
并且多提一嘴:上面现象1情况时,若断掉数据库,gorm并没有去主动尝试连接,而是当我发起新的业务请求,后台需要操作数据库时,才会检测有无可用连接,此时才会创建新的连接。
根据上面的现象,下面是我给出针对现象2的解决方法。
解决方法就是:打开数据库失败时,代表数据库没启动,此时标记打开失败,后面在业务请求来的时候,判断数据库有无打开,若无打开,重新打开即可。
下面贴出一些关键代码,DbCheck是gin的中间件,当有新的业务请求来的时候,会首先检测有无打开的gorm,若无,则打开数据库。同时,下面的Init在服务程序启动的时候,就会被调用。正常情况下,DbCheck里面的Init不会被调用到
var (
dbGorm *gorm.DB
)
func DbCheck() gin.HandlerFunc {
return func(c *gin.Context) {
if dbGorm == nil {
Init()
}
c.Next()
}
}
func Init() {
var err error
var ip, port, username, password, dbName string
ip, err = config.GetValueStr("mysql", "ip")
port, err = config.GetValueStr("mysql", "port")
username, err = config.GetValueStr("mysql", "username")
password, err = config.GetValueStr("mysql", "password")
dbName, err = config.GetValueStr("mysql", "db")
var portInt int
portInt, err = strconv.Atoi(port)
dbGorm, err = DbConn(username, password, ip, dbName, portInt)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("connnect db succeed")
//dbGorm.CreateTable(&model.OUserInfo{})
}
}
func GetDB() (db *gorm.DB) {
return dbGorm
}
func DbConn(MyUser, Password, Host, Db string, Port int) (db *gorm.DB, err error) {
connArgs := fmt.Sprintf("%s:%s@(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", MyUser, Password, Host, Port, Db)
db, err = gorm.Open("mysql", connArgs)
if err != nil {
if db != nil {
db.Close()
db = nil
}
return
}
db.SingularTable(true)
return
}