我应该将 context.Context 传递给 Go 中的底层数据库方法吗?

ALH*_*ALH 2 go mongodb go-context

我在这里使用半代码只是为了表明我对代码中发生的事情的意图,而不是使问题中的事情复杂化。

我有一个main.go文件调用连接到 mongoDB 数据库的方法:

mStore := store.NewMongoStore()
Run Code Online (Sandbox Code Playgroud)

在我有用于连接数据库的NewMongoStore上下文中:client.Connect

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
Run Code Online (Sandbox Code Playgroud)

现在,main.go我通过这种方式将商店传递给我的路由器控制器文件:

routes.GenericRoute(router, mStore)
Run Code Online (Sandbox Code Playgroud)

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

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

现在,CreateUser我再次创建一个上下文,如下所示,将文档插入到 MongoDB 中:

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

insertedId, err := repo.CreateUser(ctx, newUser{"John", "Doe"})
Run Code Online (Sandbox Code Playgroud)

在这里,我传递了上下文来createUser插入新文档。

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

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

Kas*_*Sun 5

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

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

强制为所有函数添加一个参数有点矫枉过正Context(没有必要添加Context到一个简单的greatestCommonDivisor函数中),但是将Context参数添加到您需要的任何地方永远不会有什么坏处。Context具有足够好的性能,对于您的用例(HTTP 服务器和数据库写入),它不应该对您的服务造成可见的开销。