我认为在Golang中管理数据库连接池时遇到严重问题。我使用Gorilla网络工具包构建了RESTful API,当只有很少的请求被发送到服务器时,该工具非常有用。但是现在我开始使用loader.io网站执行负载测试。对于冗长的帖子,我深表歉意,但我想为您提供完整的介绍。
在继续之前,这里是运行API和MySQL的服务器上的一些信息:专用主机Linux 8GB RAM Go版本1.1.1使用go-sql-driver MySQL 5.1的数据库连接
使用loader.io,我可以每15秒发送1000个GET请求,而不会出现问题。但是,当我发送1000个POST请求/ 15秒时,我收到很多错误,所有错误都是错误1040,数据库连接过多。许多人在线上报告了类似问题。请注意,目前我仅对一个特定的POST请求进行测试。对于此发布请求,我确保了以下内容(许多在线其他人也建议这样做)
我确保我没有使用短暂的函数打开和关闭* sql.DB。因此,正如您在下面的代码中看到的那样,我只为连接池创建了全局变量,尽管我愿意在这里提出建议,因为我不喜欢使用全局变量。
我确保尽可能使用db.Exec,并且在预期结果时仅使用db.Query和db.QueryRow。
由于上述方法无法解决我的问题,因此我尝试设置db.SetMaxIdleConns(1000),该问题解决了1000次POST请求/ 15秒的问题。意味着不再有1040错误。然后,我将负载增加到2000个POST请求/ 15秒,然后再次开始出现错误1040。我试图增加db.SetMaxIdleConns()中的值,但这没有任何区别。
在这里,我通过运行SHOW STATUS WHERE variable_name=’Threads_connected’ 从MySQL数据库获得有关连接数量的一些连接统计信息;
variable_name
对于1000个POST请求/ 15秒:观察到的#threads_connected〜= 100对于2000个POST请求/ 15秒:观察到的#threads_connected〜= 600
我还在my.cnf中增加了MySQL的最大连接数,但这没什么不同。你有什么建议?代码看起来不错吗?如果是,则可能是连接受到限制。
您将在下面找到代码的简化版本。
var db *sql.DB func main() { db = DbConnect() db.SetMaxIdleConns(1000) http.Handle("/", r) err := http.ListenAndServe(fmt.Sprintf("%s:%s", API_HOST, API_PORT), nil) if err != nil { fmt.Println(err) } } func DbConnect() *sql.DB { db, err := sql.Open("mysql", connectionString) if err != nil { fmt.Printf("Connection error: %s\n", err.Error()) return nil } return db } func PostBounce(w http.ResponseWriter, r *http.Request) { userId, err := AuthRequest(r) //error checking //ready requesy body and use json.Unmarshal bounceId, err := CreateBounce(userId, b) //return HTTP status code here } func AuthRequest(r *http.Request) (id int, err error) { //parse header and get username and password query := "SELECT Id FROM Users WHERE Username=? AND Password=PASSWORD(?)" err = db.QueryRow(query, username, password).Scan(&id) //error checking and return } func CreateBounce(userId int, bounce NewBounce) (bounceId int64, err error) { //initialize some variables query := "INSERT INTO Bounces (.....) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" result, err := db.Exec(query, ......) //error checking bounceId,_ = result.LastInsertId() //return }
Go database/sql不会阻止您创建与数据库的无限数量的连接。如果池中有一个空闲连接,则将使用该连接,否则将创建一个新连接。
database/sql
因此,在负载下,您的请求处理程序sql.DB可能找不到任何空闲连接,因此在需要时会创建一个新连接。这有点麻烦-尽可能重用空闲连接,并在需要时创建新连接- 最终达到Db的最大连接数。而且,不幸的是,在Go 1.1中,没有一种简便的方法(例如SetMaxOpenConns)来限制打开的连接。
升级到更高版本的Golang。在Go 1.2+中,您将获得SetMaxOpenConns。并查看MySql文档以开始设置,然后进行调整。
db.SetMaxOpenConns(100) //tune this
如果必须使用Go 1.1,则需要确保您的代码一次*sql.DB仅可被N个客户端使用。
*sql.DB