【问题标题】:Golang RESTful API load testing causing too many database connectionsGolang RESTful API 负载测试导致数据库连接过多
【发布时间】:2014-12-21 05:53:56
【问题描述】:

我认为我在 Golang 中管理数据库连接池时遇到了严重问题。我使用 Gorilla Web 工具包构建了一个 RESTful API,当只有很少的请求被发送到服务器时,它非常有效。但现在我开始使用 loader.io 站点执行负载测试。对于这篇长篇文章,我深表歉意,但我想为您提供完整的图片。

在继续之前,这里有一些关于运行 API 和 MySQL 的服务器的信息: 专用主机 Linux 8GB 内存 转到版本 1.1.1 使用 go-sql-driver 进行数据库连接 MySQL 5.1

使用 loader.io 我可以发送 1000 个 GET 请求/15 秒而不会出现问题。但是当我发送 1000 个 POST 请求/15 秒时,我收到很多错误,所有这些错误都是 ERROR 1040 too many database connections。许多人在网上报告了类似的问题。请注意,我现在只测试一个特定的 POST 请求。对于这个帖子请求,我确保了以下内容(许多其他人也在网上提出了建议)

  1. 我确保我不使用 Open and Close *sql.DB 来实现短期功能。所以我只为连接池创建了全局变量,如下面的代码所示,尽管我在这里接受建议,因为我不喜欢使用全局变量。

  2. 我确保尽可能使用 db.Exec,并且仅在预期结果时使用 db.Query 和 db.QueryRow。

由于上述没有解决我的问题,我尝试设置 db.SetMaxIdleConns(1000),解决了 1000 个 POST 请求/15 秒的问题。意味着不再出现 1040 错误。然后我将负载增加到 2000 个 POST 请求/15 秒,我又开始收到 ERROR 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 
}

【问题讨论】:

    标签: mysql sql go gorilla


    【解决方案1】:

    Go database/sql 不会阻止您创建无限数量的数据库连接。如果池中有空闲连接,将使用它,否则创建一个新连接。

    因此,在负载下,您的请求处理程序 sql.DB 可能找不到空闲连接,因此在需要时会创建一个新连接。这会搅动一点 - 尽可能重用空闲连接并在需要时创建新连接 - 最终达到 Db 的最大连接数。而且,不幸的是,在 Go 1.1 中没有方便的方法(例如SetMaxOpenConns)来限制打开的连接。

    升级到更新版本的 Golang。在Go 1.2+ 你得到SetMaxOpenConns。和check out the MySql docs for starting setting 然后调。

    db.SetMaxOpenConns(100) //tune this
    

    如果您必须使用 Go 1.1,则需要在您的代码中确保 *sql.DB 一次只被 N 个客户端使用。

    【讨论】:

    • 谢谢@MattSelf。我将更新 Go 到 1.2 并再次测试。我会回来报告我的发现。
    • 附带问题@MattSelf。你知道像 Facebook 这样的应用程序大约有多少允许的数据库连接吗?
    • 您是在问 SetMaxOpenConns 的合理设置是多少?因为答案是它取决于平台(即您部署 MySql 的位置)。见dev.mysql.com/doc/refman/5.5/en/too-many-connections.html。我将其设置为 100,然后进行调整。像 Facebook 这样的应用程序属于自己的一类,达到超过 1000 万请求/秒,我很容易猜到超过 20000 个连接,因为它们已经调整并且运行的机器与您的示例完全不同(例如,可能超过 72GB+ RAM,24 个磁盘 raid10) .
    • 另外,如果我要升级,我会借此机会升级到 Golang 的最新稳定版本(例如 1.3)。
    【解决方案2】:

    @MattSelf 提出的解决方案是正确的,但我遇到了其他问题。在这里,我强调了我为解决问题所做的一切(顺便说一下,服务器正在运行 CentOS)。

    1. 由于我有一个专用服务器,我增加了 MySQL 的 max_connections

    在 /etc/my.cnf 中,我添加了 max_connections=10000 行。虽然,这比我需要的要多。

    1. 重启 MySQL:service mysql restart

    2. 更改了 ulimit -n。那就是增加打开的描述文件的数量。

    为此,我对两个文件进行了更改:

    在 /etc/sysctl.conf 我添加了这一行

    fs.file-max = 65536
    

    在 /etc/security/limits.conf 我添加了以下几行:

    *          soft     nproc          65535
    *          hard     nproc          65535
    *          soft     nofile         65535
    *          hard     nofile         65535
    
    1. 重启你的服务器

    2. 按照@MattSelf 的建议升级到 1.3.3

    3. 设置

        db.SetMaxOpenConns(10000)
      

    再一次,这个数字对于我的需要来说太大了,但这向我证明了一切正常。

    1. 我使用 loader.io 运行了一个测试,该测试由 5000 个客户端组成,每个客户端都在 15 秒内发送 POST 请求。一切顺利完成。

    【讨论】:

      【解决方案3】:

      另外需要注意的是在 my.cnf 文件中将 back_log 设置为更高的值,例如几百或 1000。这将有助于每秒处理更多的连接。见High connections per second

      【讨论】:

        猜你喜欢
        • 2012-04-20
        • 2021-03-19
        • 2017-05-04
        • 2011-04-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多