详解若安在Go处事中做链路追踪
独霸 Go 言语斥地微处事的详解时辰 ,需求追踪每个请求的若安访谒链路,这块在 Go 中如今没有很好的处事措置筹划。
在 Java 中措置这个问题斗劲复杂 ,中做追踪可独霸 MDC ,链路在一个过程内共享一个请求的详解 RequestId。
在 Go 中完成链路追踪有两种思路:一种是若安在项目中独霸一个全局的 map, key 是处事 goroutine 的独一 Id,value 是中做追踪 RequestId,此外一种思路可独霸 context.Context 来完成。链路
上面的详解代码基于 gin 框架来完成。
1. 独霸全局 map 来完成
独霸 map 筹划需求在全局呵护一个 map ,若安在一个请求出往的处事时辰,会为每个请求生成 RequestId,中做追踪然后在每次在打印日记的链路时辰 ,从这个 Map 中经由过程 goid 取掉落踪掉落踪 RequestId,打印到日记中 。
代码的完成很复杂:
var requestIdMap = make(map[int64]string) // 全局的 Mapfunc main() { r := gin.Default() r.Use(Logger()) // 独霸中心件 r.GET("/index", func(c *gin.Context) { Info("main goroutine") // 打印日记 c.JSON(200, gin.H{ "message": "index", }) }) r.Run()}func Logger() gin.HandlerFunc { return func(c *gin.Context) { requestIdMap[goid.Get()] = uuid.New().String() // 在日记中心件中为每个请求设定 c.Next() }}func Info(msg string) { now := time.Now() nowStr := now.Format("2006-01-02 15:04:05") fmt.Printf("%s [%s] %s\n", nowStr, requestIdMap[goid.Get()], msg) // 打印日记}多么的完成很复杂,可是问题也良多 。
第一个问题就是,在 Go 法度圭表类型中 ,一次请求大年夜大年夜约会触及到多个 goroutine ,用这类编制很难在多个 gotoutine 之间传递 RequestId 。
不才面的代码中,假定新启动了一个 goroutine ,就会招致日记中掉落踪掉落踪不到 RequestId:
func main() { r := gin.Default() r.Use(Logger()) r.GET("/index", func(c *gin.Context) { Info("main goroutine") go func() { // 这里新启动了一个一个 goroutine Info("goroutine1") }() c.JSON(200, gin.H{ "message": "index", }) }) r.Run()}掉落踪掉落踪 goroutine id 也不是一种常例的做法,一样往常要经由过程 hack 的编制来掉落踪掉落踪,这类做法已不推荐了。并且这个全局的 map 为了并发安然,在理论的独霸中 ,可以还需求用到锁,在高并发的气候下必定会影响功用。
在每个请求停止的时辰,还需求手动的把 requestId 从 map 中删除,不然就会构成内存泄漏。
总的来讲,独霸 map 这类编制来完成真实不是很好。
2. 独霸 Context 来完成
不才面的代码中 ,我们独霸一个 hack 的编制往掉落踪掉落踪 goroutine id ,这类编制早就不推荐独霸,更推荐独霸 Context,关于 Context 内容 ,可以往看我之前的文章,在这里就不多说了 。
在传递 RequestId 的场景中 ,一样也可独霸 Context 来完成,独霸 Context 益处很较着,Context 生命周期与请求不异 ,不须要手动烧毁 。并且Context 是每个请求独享的,也不必忧虑并发安然的问题,Context 还可以在 goroutine 之间传递 。
独霸 Context 完成的代码以下 :
func main() { r := gin.Default() r.Use(Logger()) r.GET("/index", func(c *gin.Context) { ctx, _ := c.Get("ctx") Info(ctx.(context.Context) , "main goroutine") go func() { Info(ctx.(context.Context), "goroutine1") }() c.JSON(200, gin.H{ "message": "index", }) }) r.Run()}func Logger() gin.HandlerFunc { return func(c *gin.Context) { valueCtx := context.WithValue(c.Request.Context(), "RequestId", uuid.New().String()) c.Set("ctx", valueCtx) c.Next() }}func Info(ctx context.Context, msg string) { now := time.Now() nowStr := now.Format("2006-01-02 15:04:05") fmt.Printf("%s [%s] %s\n", nowStr, ctx.Value("RequestId"), msg)}多么在一个请求中,全数的 gotroutine 都可以取掉落踪掉落踪不合个 RequestId,并且不必忧虑内存泄漏和并发安然。
可是独霸 Context 也有个问题就是需求每次传递 Context,良多人还不习惯独霸这类编制 。真实 Go 官方早就推荐独霸 Context了,但凡会把 Context 作为函数的第一个参数。假定函数独霸筹划体作为参数 ,也可以直接把 Context 作为筹划体的一个字段 。
Context 除独霸可以同来传递 RequestId 以外 ,还可以用来独霸 goroutine 的生命周期 ,这些内容在之前的 Context 文章中具体说了然,感欢欣乐乐喜悦爱好的可以往看看。
3. 小结
掉落踪掉落踪 goroutine id 这类编制理应被扔掉落踪,而是理应独霸 Context, Go 官方也早就推荐独霸这类编制 ,在上文中,我们独霸 Context 来传递 RequestId,除此以外还可以用来传递单个请求局限的值 ,比如认证的 token 之类的 ,理应习惯在代码中独霸 Context 。
[1] https://blog.golang.org/context
到此这篇关于详解若安在Go 处事中做链路追踪的文章就引见到这了,更多相干Go 处事中做链路追踪内容请搜刮完竣下载之前的文章或延续不雅不雅不雅不雅鉴赏上面的相干文章希看大年夜师往后多多支撑完竣下载!
