У меня есть давно работающий демон, написанный на Go, который прослушивает порт и запускает несколько процедур go для каждого нового подключения для обработки данных. Существует глобальная переменная db
, которой назначается контекст соединения, возвращаемый функцией database/sql
библиотеки open()
в функции main() моего скрипта.
Мы храним пароль базы данных в хранилище, которое меняет его каждые пару дней из соображений безопасности. Я могу получить пароль из хранилища при первом создании контекста подключения, и этот же контекст используется во всех процедурах go для создания новых подключений к БД. Однако, когда пароль меняется хранилищем, все новые подключения к базе данных терпят неудачу. Я хотел бы знать, как лучше всего справиться с этим, чтобы он извлекал пароль из хранилища при сбое и повторно подключался. Если бы это был язык oop, я мог бы расширить библиотеку db и переопределить функцию подключения, чтобы перехватить ошибку и получить пароль из хранилища при сбое подключения. Есть ли аналогичный подход, который можно использовать в Go, или есть ли другой способ справиться с этим? Я очень новичок в Go, и прошу прощения, если я не правильно сформулировал вопрос.
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"net"
)
var db *sql.DB
const port = "port number"
func main() {
db, err = sql.Open("mysql","<Connection string that contains the password fetched from vault>")
db.SetMaxOpenConns(100)
listener, err := net.Listen("tcp", ":"+port)
for {
conn, err := listener.Accept()
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
// Uses db variable to connect to db.
}
Зачем вам создавать контекст на main? почему бы не создать отдельный пакет/пространство имен и выполнить там подключения к базе данных. создайте новое пространство имен с методами... createDBClient() создает новые контексты базы данных, извлекая пароль из хранилища и сохраняя его в глобальной переменной db. GetDBClient () проверяет, есть ли что-то в глобальной переменной db, если да, проверьте, может ли она подключиться к db (https://golang.org/pkg/database/sql/#DB.Ping) если не вызвать цикл createDBClient(), это несколько раз со сном, если не подключается сбой и запись если он соединяется, верните контекст
теперь в вашем handleConnection() вызовите db := newnamespace.GetDBClient()
sql.Open
не создает соединение. Он только создает контекст. Соединение создается при выполнении запроса, что означает в handleConnection()
. Я могу поймать ошибку, а затем повторно подключиться, но я не хочу ждать, пока запрос не сможет повторно подключиться с новым паролем, а вместо этого поймать ошибку неправильного подключения, а затем создать новое подключение, извлекая пароль из хранилища. Однако я не уверен, как я могу поймать ошибку плохого соединения, поскольку библиотека не выдает ее (может быть, я ошибаюсь).
да правда, я хотел сказать контекст. обновленный ответ с конкретными деталями
Означает ли это, что нам нужно пинговать перед каждым выполнением sql, чтобы проверить, живо ли соединение? Шансы на его сбой очень редки, поэтому пинговать каждый раз невозможно. Вот почему я хотел поймать ошибку плохого соединения, а затем снова подключиться. Библиотека проверяет, активно ли соединение, и, если нет, пытается создать новое соединение с сохраненным контекстом. Все, что я хотел сделать, это когда библиотека поймает ошибку плохого соединения и создаст новое соединение, заставить ее использовать новый пароль, который может решить проблему.
не при каждом sql-запросе, непосредственно перед тем, как вы начнете выполнять базовые запросы внутри вашего handleConnection(), поэтому каждый раз, когда вы выполняете этот метод.
Это долго работающий демон, и этот метод запускается всякий раз, когда устанавливается новое соединение tcp, что происходит почти каждый раз.
«Все, что я хотел сделать, это когда библиотека обнаружит ошибку плохого соединения и создаст новое соединение, заставить ее использовать новый пароль, который может решить проблему». тогда вам нужно дождаться сбоя запроса, а затем повторить попытку. либо проверьте перед тем, что безопаснее. ping только проверяет состояние соединения, не выполняет никаких запросов, или вам нужно дождаться его сбоя. я предпочитаю пинг сбою.
Согласитесь, что проверка связи — хорошая идея, но не для этого сценария из-за различных других ограничений, и мы подумали об этом. Ожидание сбоя запроса — это решение, которое мы имели в виду, но мы хотели улучшить его, обрабатывая его на уровне установления соединения. В языках oop мы могли бы сделать это, расширив библиотеку и переопределив функцию. Тем не менее, мои навыки го не так хороши, и я все еще учусь, и я не уверен, что в голанге есть эквивалентный подход к композиции.
Я решил эту проблему, создав собственный драйвер, который импортирует github.com/go-sql-driver/mysql и регистрируется в пакете sql, так что, когда требуется новое соединение, пользовательский драйвер может получить пароль из хранилища и передать это до драйвера mysql, чтобы открыть соединение. См. пример кода ниже.
package vault-mysql-driver
import (
"database/sql"
"github.com/go-sql-driver/mysql"
)
type VaultMysqlDriver struct {
*mysql.MySQLDriver
}
func updateDsn(dsn string) (string, err) {
// logic to fetch password from vault and update dsn with the password
}
func (d VaultMysqlDriver) Open(dsn string) (driver.Conn, error) {
updateddsn, err := updateDsn(dsn)
// Pass down the dsn with password to mysql driver's open function
return d.MySQLDriver.Open(updateddsn)
}
// When initialised will register the driver in sql package
func init() {
sql.Register(vault-driver, &CyberarkMysqlDriver{&mysql.MySQLDriver{}})
}
Этот пакет теперь импортируется в демон, как показано ниже:
import (
"database/sql"
_ "vault-mysql-driver"// init is invoked and it will get registered in sql package
"net"
)
var db *sql.DB
const port = "port number"
func main() {
// vault-driver is used instead of mysql so that the sql package knows to use the custom driver for new connections.
db, err = sql.Open("vault-driver","<Connection string that contains the password fetched from vault>")
db.SetMaxOpenConns(100)
listener, err := net.Listen("tcp", ":"+port)
for {
conn, err := listener.Accept()
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
// Uses db variable to connect to db.
}
Таким образом, всякий раз, когда хранилище меняет пароль, не будет сбоя соединения, поскольку драйвер хранилища всегда будет извлекать пароль из хранилища для каждого нового соединения.
также причина, по которой я сказал создать другой пакет/пространство имен, заключается в том, что если вы собираетесь сделать var db глобальным, то все, что находится в main, может получить к нему доступ и изменить, если оно находится в другом пространстве имен, а переменная или метод, начинающийся с нижнего регистра, становится частным для этого пространства имен, так намного безопаснее.