Обработка ошибок подключения к серверу баз данных
Как в Golang правильно обрабатывать ошибки коннекта к серверу баз данных? Вот я например использую MySQL и gorm в качестве ORM. Соответственно, драйверами выступают github.com/go-sql-driver/mysql и встроенный database/sql/driver. Все, что я нарыл по ошибкам коннекта это то, что можно проверять две ошибки - driver.ErrBadConn и mysql.ErrInvalidConn:
errors.Is(err, driver.ErrBadConn)
errors.Is(err, mysql.ErrInvalidConn)
Но чем одна ошибка отличается от другой - вообще непонятно. И при каких обстоятельствах они возникают - тоже непонятно. У себя на машине я сумел воспроизвести ErrInvalidConn ("invalid connection") полным выключением\остановкой сервера MySQL. Однако у клиента такая ошибка возникает и тогда, когда сервер находится в активном состоянии.
Пример ошибки, если принудительно остановить сервер БД:
[mysql] 2021/04/21 14:11:36 packets.go:36: read tcp 127.0.0.1:58279->127.0.0.1:3306: wsarecv: An established connection was aborted by the software in your host machine.
[mysql] 2021/04/21 14:11:36 packets.go:36: read tcp 127.0.0.1:58285->127.0.0.1:3306: wsarecv: An established connection was aborted by the software in your host machine.
←[33m[2021-04-21 14:11:36]←[0m ←[31;1m invalid connection ←[0m
FATA[04/21 14:11:36] euro.go:1424 [INSERT LOAD] invalid connection => &errors.errorString{s:"invalid connection"}
Ответы (2 шт):
Читаем документацию
The driver interface has evolved over time. Drivers should implement Connector and DriverContext interfaces. The Connector.Connect and Driver.Open methods should never return ErrBadConn. ErrBadConn should only be returned from Validator, SessionResetter, or a query method if the connection is already in an invalid (e.g. closed) state.
Connector.ConnectиDriver.Openметоды не должны никогда возвращатьErrBadConn. ErrBadConn возвращается только изValidator,SessionResetter, или запроса если соединение уже невалидно (например, закрыто).
Если вы получаете такую ошибку, значит установленное соединение у вас разорвано. Либо из-за проблем в сети, либо по инициативе сервера
Поскольку best practice никто не предоставил (а именно этого я ожидал, а не отсылок в доку) - предоставляю собственное решение обработки ошибок коннекта к серверу БД:
В функции main создается канал для записи ошибки и в нем производится попытка достучаться до сервера, если не получается - новый коннект (gorm.Open("mysql", connectStr) и т.д.)
app.errChan = make(chan error)
go func() {
for {
<-app.errChan
err := app.mysql.Ping()
if err != nil {
log.Warn("[DB OPEN ] TRYING TO RECONNECT...")
dbGorm, err := app.mysql.Reconnect()
if err == nil {
app.mysql.DB = dbGorm
log.Info("[DB OPEN ] CONNECTION RESTORED")
}
}
time.Sleep(2 * time.Second)
}
}()
А здесь проверяем ошибку и если она удовлетворяет нашим паттернам - пишем ее в канал (кто не успел записать - блокируется на таймере) и делаем перезаход каждой горутины на обращение к БД.
insertOrUpdate:
action, err = app.mysql.InsertOrUpdate(
app.C.Database.TableName,
"itemid = ?",
[]interface{}{doc.ItemId},
court,
)
if err != nil {
if errors.Is(err, driver.ErrBadConn) ||
errors.Is(err, mysql.ErrInvalidConn) ||
strings.Contains(err.Error(), "connectex") {
rand.Seed(time.Now().UnixNano())
blockTime := rand.Intn(5 - 1) + 1
if tryTimes >= 0 { // всего у нас N попыток
select {
// we write an error to the channel and after a second
// we try to make a new request to the database
case app.errChan <- err:
time.Sleep(1 * time.Second)
tryTimes--
log.Warnf("[RECONNECT] THERE ARE [%02s] RECOVERY ATTEMPTS LEFT...", utils.Red(tryTimes))
goto insertOrUpdate
// here we block the rest of the goroutines
case <-time.After(time.Duration(blockTime) * time.Second):
goto insertOrUpdate
}
} else {
app.KillDaemons()
log.Fatalf("[WRITE %s] %s => %#v",
strings.ToUpper(action),
err.Error(), err)
}
}
}
Такая схема вполне работает для меня. Но если есть решения лучше - вы можете их предложить.