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

时间:2014-10-25 15:34:04

标签: mysql sql go gorilla

我认为我在Golang中管理数据库连接池存在严重问题。我使用Gorilla Web工具包构建了一个RESTful API,当只有少量请求被发送到服务器时,它工作得很好。但是现在我开始使用loader.io站点进行负载测试。我为长篇文章道歉,但我想全面了解你。

在进一步讨论之前,以下是运行API和MySQL的服务器上的一些信息: 专用主机Linux 8GB RAM 转到1.1.1版 使用go-sql-driver进行数据库连接 MySQL 5.1

使用loader.io我可以发送1000 GET请求/ 15秒没有问题。但是当我发送1000个POST请求/ 15秒时,我会收到很多错误,所有错误都是ERROR 1040太多的数据库连接。许多人在网上报道了类似的问题。请注意,我现在只测试一个特定的POST请求。对于这篇帖子请求,我确保了以下内容(许多其他人也在线提出)

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

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

  3. 由于上面没有解决我的问题,我尝试设置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 
    }
    

3 个答案:

答案 0 :(得分:8)

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,则需要在代码中确保{N}个客户端一次只能使用*sql.DB

答案 1 :(得分:1)

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

  1. 由于我有一个专用服务器,我增加了MySQL的max_connections
  2. 在/etc/my.cnf中我添加了行max_connections = 10000。虽然,这比我需要的更多。

    1. 重启MySQL:service mysql restart

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

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

      在/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

      3. 的建议转到1.3.3
      4. 设置

          db.SetMaxOpenConns(10000)
        
      5. 这个数字对于我需要的数字来说太大了,但这证明了事情的成功。

        1. 我使用loader.io运行测试,该测试包含5000个客户端,每个客户端都在15秒内发送POST请求。一切都没有错误。

答案 2 :(得分:1)

还需要注意的是将my_f文件中的back_log更高的值设置为几百或1000.这将有助于每秒处理更多连接。见High connections per second