一尘不染

Golang RESTful API负载测试导致数据库连接过多

go

我认为在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请求进行测试。对于此发布请求,我确保了以下内容(许多在线其他人也建议这样做)

  1. 我确保我没有使用短暂的函数打开和关闭* sql.DB。因此,正如您在下面的代码中看到的那样,我只为连接池创建了全局变量,尽管我愿意在这里提出建议,因为我不喜欢使用全局变量。

  2. 我确保尽可能使用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数据库获得有关连接数量的一些连接统计信息;

对于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 
}

阅读 269

收藏
2020-07-02

共1个答案

一尘不染

Go 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个客户端使用。

2020-07-02