Managing connections per request in Go

huangapple go评论85阅读模式
英文:

Managing connections per request in Go

问题

假设来说,每次请求都连接到数据库并在请求完成后关闭,这样做是否是一个好的实践?

我正在使用mongodbmgo来处理数据库。

在我的项目中,我希望通过从请求头中获取数据库名称来连接到特定的数据库(当然,这需要与身份验证机制结合,比如我的应用中使用的JWT)。流程大致如下:

  1. 用户身份验证:

    发送 POST 请求到 http://api.app.com/authenticate
    // 这个请求会在一个“全局”数据库中检查用户,
    // 对其进行身份验证,并返回一个签名的JWT令牌
    // 该令牌存储在bolt.db中,用于身份验证机制

  2. 一些 RESTful 操作:

    发送 POST 请求到 http://api.app.com/v1/blog/posts
    // 每个对 /v1* 的请求都会设置JWT中间件
    // 请求头中的 Client-Domain 设置为数据库的名称,例如 'app-com'
    // 因此,我们会连接到该数据库,并在请求完成后关闭连接

所以我的问题是:

  1. 这种做法可行吗?- 我已经了解了连接池和重用连接的概念,但还没有深入研究过
  2. 是否有更好的方法来实现所需的功能?
  3. 如何确保会话仅在请求完成后关闭?

我之所以需要这样做,是因为我们有多个供应商,他们拥有相同的数据库集合,但具有不同的条目,并且只能访问自己的数据库。

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

类似于:

func main() {
    ...
    // 配置连接并设置为全局变量
    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() 是 ds.session.Close() 的别名,此处未包含该函数的代码
    // 我仍在对此进行实验,我需要确保会话仅在请求完成后关闭,目前并不总是如此
    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() 是 ds.db.C() 的别名,此处未包含该函数的代码
    db.C("system_users").Find(nil).All(&users)
}

与我最初尝试的方法相比,响应时间减少了40%。

英文:

Hypothetically speaking, is it good practice to connect to a database for each request and close in when the request has completed?

I'm using mongodb with mgo for the database.

In my project, I would like to connect to a certain database by getting the database name from the request header (of course, this is combined with an authentication mechanism, e.g. JWT in my app). The flow goes something like:

  1. User authentication:

     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. Some RESTful operations

     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
    

So my questions are:

  1. Is this feasible? - I've read about connection pools and reusing them but I haven't read much about them yet
  2. Is there a better way of achieving the desired functionality?
  3. How do I ensure the session is only closed when the request has completed?

The reason why I need to do this is because we have multiple vendors that have the same database collections with different entries with restricted access to their own databases.

Update / Solution
I ended up using Go's built in Context by Copying a session and using it anywhere I need to do any CRUD ops

Something like:

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
}

And then use it in a HandlerFunc, example /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% response time decrease over the original method I experimented with.

答案1

得分: 0

假设来说,这不是一个好的做法,因为:

  1. 数据库逻辑分散在几个包中。
  2. 很难进行测试。
  3. 无法应用 DI(主要是代码维护困难)。

回答你的问题:

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

Hypothetically speaking is not a good practice because:

  1. The database logic is scattered among several packages.
  2. It's difficult to test
  3. You can't apply DI (mainly it will be hard to maintain the code)

Replying to your questions:

  1. Yes is feasible BUT you will not use the connection pool inside them go package (take a look to the code here if you want know more about Connection Pool)
  2. A better way is to create a global variable that contains the database connection and close when the application is going to stop (and not close the connection every request)
  3. How do I ensure the session is only closed when the request has complete<- you should checkout the answer fro your db query and then close the connection (but I don't recommend to close the connection after a request because you'll need to open again for another request and close again etc...)

huangapple
  • 本文由 发表于 2017年2月14日 17:50:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/42222797.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定