在Go语言中,是否应该将context.Context传递给底层的数据库方法?

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

Should I pass context.Context to underlying DB methods in Go?

问题

我在这里只是为了展示代码中发生的事情,而不是让问题变得复杂,所以我使用了半代码。我有一个main.go文件,调用一个连接到MongoDB数据库的方法:

mStore := store.NewMongoStore()

NewMongoStore中,我使用上下文client.Connect连接到数据库:

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

现在在main.go中,我将store传递给我的路由控制器文件:

routes.GenericRoute(router, mStore)

GenericRoute中,我获取mStore并将其传递给函数处理程序:

func GenericRoute(router *gin.Engine, mStore store.Store) {
    router.POST("/users", controllers.CreateUser(mStore))
}

现在在CreateUser中,我再次创建一个上下文,以便将文档插入MongoDB:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

insertedId, err := repo.CreateUser(ctx, newUser{"John", "Doe"})

在这里,我将上下文传递给createUser以插入新文档。

正如你所看到的,在某些部分我传递了上下文,在某些部分我没有。我真的不知道该怎么办?在处理上下文方面,什么是正确的方法?我是否应该始终传递上下文,或者像这样创建新的上下文以避免在方法参数中传递上下文?

在这种编码方式中,最佳实践是什么?从性能的角度来看,哪种方式更好?

英文:

I use semi-code here just to show my intention of whats going on in code and not complicating things here in the question.

I have a main.go file that calls a method that connects to mongoDB database:

mStore := store.NewMongoStore()

In NewMongoStore I have context that client.Connect uses to connect to database:

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

Now in main.go I pass the store to my router controller file this way:

routes.GenericRoute(router, mStore)

In GenericRoute I get the mStore and pass it to function handlers:

func GenericRoute(router *gin.Engine, mStore store.Store) {
	router.POST("/users", controllers.CreateUser(mStore))
}

Now in CreateUser I again create a context as below to insert document into MongoDB:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

insertedId, err := repo.CreateUser(ctx, newUser{"John", "Doe"})

Here I passed context to createUser to insert a new document.

As you see in some parts I have passed context and in some parts I did not. I really do not have any idea what I should do? What is the correct way to work with contexts? Should I always pass context around or it is totally ok to create new contexts like this to not pass context in method parameters.

What is the best practice for this kind of coding? And which one is better from performance point of view?

答案1

得分: 4

根据我的经验,Context 有两个主要的用途:

  1. 传递信息。对于你的问题,你可能想为每个请求生成一个 request_id,并将其传递到代码的最低层,以便记录此 request_id 以进行整个代码库的错误跟踪。
    1. 这个特性并不总是有用的,例如你想初始化一个 MongoDB 连接,但是这是在服务启动时完成的。此时没有有意义的上下文,一个带有超时的 context.Background 应该足够好。
    2. 当从 Context 中获取的值发生变化时要小心,如果你在各个地方传递 Context,这可能会导致并发访问。
  2. 自动取消和超时。这两个特性并不是无中生有的,你需要调整你的代码来处理来自 Context 的这些信息。但是大多数带有 Context 参数的第三方和标准库可以优雅地处理这两个特性(例如数据库库、HTTP 调用库)。有了这个特性,一旦 Context 失效,你就可以自动回收资源。
    1. 有时你会希望停止这种级联行为,例如在后台 goroutine 中写日志,然后你需要创建一个新的 context.Background() 来避免这些写操作在上游上下文取消时被取消。context.Background() 还会清除信息上下文,所以有时你需要从上游上下文中提取上下文信息,并手动将它们附加到这个新的上下文中。

强制在所有函数中添加一个 Context 参数有点过度(对于一个简单的 greatestCommonDivisor 函数来说添加 Context 是没有意义的),但是在任何需要的地方添加一个 Context 参数是没有坏处的。Context 的性能足够好,在你的用例(HTTP 服务器和数据库写入)中,它不应该对你的服务造成明显的开销。

英文:

Based on my experience, Context has two major use cases:

  1. Pass down information. For your question, you might want to generate a request_id for each request and passing it down to the lowest part of your code, and logging this request_id to do error tracing across the whole code base.
    1. This feature is not always useful, for example you want to initialise a MongoDB connection, but it's done during service start-up. At this time there's no meaningful context, a context.Background with timeouts should be good enough.
    2. Be cautious with mutating values retrieved from Context, this could cause concurrent access if you're passing the Context all around.
  2. Auto-cancellation and timeouts. These two features doesn't come from nothing, you need to tune your code to handle these informations from the Context. But most third-party and standard libraries with an Context parameter can handle these two features gracefully (e.g. database libraries, HTTP call libraries). With this feature you can auto reclaim resources once the Context invalidated.
    1. Sometime you'll want to stop this cascading behaviour, for example writing logs in the background goroutine, then you need to create a new context.Background() to avoid these writes get cancelled once upstream context cancelled. context.Background() also clears the information context so sometime you need to extract the context information from the upstream context, and manually append them to this new context.

It's a bit overkill to force a Context parameter to all functions, (there's no point to add Context to a simple greatestCommonDivisor function) but adding a Context parameter to anywhere you need it never hurts. Context has good enough performance, for your use case (HTTP server & database writing), it should not cause visible overhead to your service.

答案2

得分: 0

我对自己的问题得出了一个有趣的答案,所以我希望将其放在这里,供将来有相同问题的用户参考。

如果我将连接到Mongo的相同上下文传递给userController,并将其进一步传递给CreateUser函数:

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)

**注意:**我在main函数中使用defer cancel()来取消上下文,而不是在NewMongoStore函数中取消。

10秒后,如果调用POST /users,你将得到context deadline exceeded的错误,所以基本上你不能使用这个上下文来做其他事情,你必须在每次CreateUser调用时创建新的上下文。

所以我写的是正确的。在我的示例中,我等待10秒钟来连接到mongo,然后等待1秒钟来执行插入操作的上下文。

英文:

I reached to an interesting answer to my own question, so I prefer to put here for future users if having the same question in mind.

If I pass the SAME context that I have connected to Mongo with to userController and pass it down further to CreateUser function:

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)

NOTE: instead of cancelling context in NewMongoStore function I defer cancel() it in main function.

After 10 seconds if you call POST /users you will get context deadline exceeded, so basically you cannot use this context to do other stuff and you have to create new context on each CreateUser call.

So what I have written is fine. I wait for 10 seconds to connect to mongo in my example and 1 second for my insert operation context.

huangapple
  • 本文由 发表于 2022年7月17日 15:44:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/73010023.html
匿名

发表评论

匿名网友

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

确定