在 Go 中管理每个请求的连接

Managing connections per request in Go

假设地说,为每个请求连接到数据库并在请求完成时关闭是否是一种好习惯?

我正在使用 mongodbmgo 作为数据库。

在我的项目中,我想通过从请求头中获取数据库名称来连接到某个数据库(当然,这与身份验证机制相结合,例如我的应用程序中的JWT)。流程是这样的:

  1. 用户认证:

    POST to http://api.app.com/authenticate
    // which checks the user in a "global" database,
    // authenticates them and returns a signed JWT token
    // The token is stored in bolt.db for the authentication mechanism
    
  2. 一些 RESTful 操作

    POST to http://api.app.com/v1/blog/posts
    // JWT middleware for each request to /v1* is set up
    // `Client-Domain` in header is set to a database's name, e.g 'app-com'
    // so we open a connection to that database and close when
    // request finishes
    

所以我的问题是:

  1. 这可行吗? - 我已经阅读了有关连接池和重用它们的内容,但我还没有阅读太多关于它们的内容
  2. 是否有更好的方法来实现所需的功能?
  3. 如何确保会话仅在请求完成时关闭?

我需要这样做的原因是因为我们有多个供应商拥有相同的数据库集合,但具有不同的条目,对他们自己的数据库的访问受到限制。

更新/解决方案 我最终使用 Go 内置的 Context 通过复制会话并在任何需要执行任何 CRUD 操作的地方使用它

类似于:

func main() {
    ...
    // Configure connection and set in global var
    model.DBSession, err = mgo.DialWithInfo(mongoDBDialInfo)
    defer model.DBSession.Close()
    ...

    n := negroni.Classic()
    n.Use(negroni.HandlerFunc(Middleware))

    ...
}

func Middleware(res http.ResponseWriter, req *http.Request, next http.HandlerFunc) {

    ...
    db := NewDataStore(clientDomain)
    // db.Close() is an alias for ds.session.Close(), code for this function is not included in this post
    // Im still experimenting with this, I need to make sure the session is only closed after a request has completed, currently it does not always do so
    defer db.Close()

    ctx := req.Context()
    ctx = context.WithValue(ctx, auth.DataStore, db)
    req = req.WithContext(ctx)
    ...
}

func NewDataStore(db string) *DataStore {
    store := &DataStore{
        db: DBSession.Copy().DB(db),
        session: DBSession.Copy(),
    }
    return store
}

然后在HandlerFunc中使用它,例子/v1/system/users:

func getUsers(res http.ResponseWriter, req *http.Request) {
    db := req.Context().Value(auth.DataStore).(*model.DataStore)
    users := make([]SystemUser{}, 0)
    // db.C() is an alias for ds.db.C(), code for this function is not included in this post
    db.C("system_users").Find(nil).All(&users)
}

响应时间比我试验的原始方法减少了 40%。

假设地说不是一个好习惯,因为:

  1. 数据库逻辑分散在几个包中。
  2. 很难测试
  3. 不能应用DI(主要是代码维护起来会比较麻烦)

回复您的问题:

  1. 是可行的,但是您不会使用它们 go 包中的连接池(如果您想了解有关连接池的更多信息,请查看代码 here
  2. 更好的方法是创建一个包含数据库连接的全局变量,并在应用程序将要停止时关闭(而不是每次请求都关闭连接)
  3. 如何确保会话仅在请求完成时关闭<- 您应该检查数据库查询的答案,然后关闭连接(但我不建议在请求后关闭连接,因为您需要为另一个请求再次打开并再次关闭等等...)