Featured image of post Go 链路追踪中为什么先调用 otel.GetTracerProvider() 再调用 otel.SetTracerProvider() 也能上报成功?

Go 链路追踪中为什么先调用 otel.GetTracerProvider() 再调用 otel.SetTracerProvider() 也能上报成功?

otel.GetTracerProvider() + otel.SetTracerProvider() 过程

最近在go-zero中使用了一些三方包集成内部的链路追踪, 部分代码如下

package main

import (
	"fmt"
	"github.com/zeromicro/go-zero/zrpc"
)

func main() {
	
	// 1. 获取服务依赖的配置
	svcCtx := svc.NewServiceContext()
	
	// 2. 实例化服务但配置, 实际上这里的代码会调用 otel.SetTracerProvider()
	// server.NewServer() => 
	// service.SetUp => 
	// trace.StartAgent => 
	// trace.startAgent() => 
	// otel.SetTracerProvider()
	s := zrpc.MustNewServer()
	defer s.Stop()


	fmt.Printf("Starting rpc server at %s...\n", svcCtx.Config.ListenOn)
	s.Start()
}

package svc

import (
    "time"

    "github.com/redis/go-redis/extra/redisotel/v9"
    "github.com/redis/go-redis/v9"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "gorm.io/plugin/opentelemetry/tracing"
    "github.com/zeromicro/go-zero/zrpc"
)

func NewServiceContext() {

	conn, err := gorm.Open()
	redisClient := redis.NewClient()
	
	// 1.1 增加 链路追踪
	// 这以下两个方法都会调用 otel.GetTracerProvider()
	redisotel.InstrumentTracing(redisClient)
	conn.Use(tracing.NewPlugin(tracing.WithoutMetrics()))
	
	// return xxx
}

代码追踪

  • otel包的代码也很简单, 就是为了包装一层标准, 实际上是调用了global
package otel // import "go.opentelemetry.io/otel"

import (
	"go.opentelemetry.io/otel/internal/global"
	"go.opentelemetry.io/otel/trace"
)


func Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
	return GetTracerProvider().Tracer(name, opts...)
}

func GetTracerProvider() trace.TracerProvider {
	return global.TracerProvider()
}

func SetTracerProvider(tp trace.TracerProvider) {
	global.SetTracerProvider(tp)
}

  • global包的代码也很简单, 就是为了包装一层标准, 实际上是调用了global
  • 代码也没什么特别的, 只是使用了原子返回了一个默认的实例
  • 最主要的就是SetTracerProvider方法, 它会通过TracerProvider拿到当前的实例(gorm,redis已经用的那个)
  • 然后把当前要设置的tp传递给原来的的那个(且只会执行一次)
package global // import "go.opentelemetry.io/otel/internal/global"

import (
	"sync"
	"sync/atomic"

	"go.opentelemetry.io/otel/metric"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/trace"
)

var (
	globalTracer        = defaultTracerValue()
	delegateTraceOnce             sync.Once
	delegateTextMapPropagatorOnce sync.Once
	delegateMeterOnce             sync.Once
)

type (
	tracerProviderHolder struct {
		tp trace.TracerProvider
	}

	propagatorsHolder struct {
		tm propagation.TextMapPropagator
	}

	meterProviderHolder struct {
		mp metric.MeterProvider
	}
)

func TracerProvider() trace.TracerProvider {
	return globalTracer.Load().(tracerProviderHolder).tp
}

func SetTracerProvider(tp trace.TracerProvider) {
	current := TracerProvider()

	if _, cOk := current.(*tracerProvider); cOk {
		if _, tpOk := tp.(*tracerProvider); tpOk && current == tp {
			// Do not assign the default delegating TracerProvider to delegate
			// to itself.
			Error(
				errors.New("no delegate configured in tracer provider"),
				"Setting tracer provider to its current value. No delegate will be configured",
			)
			return
		}
	}

	delegateTraceOnce.Do(func() {
		if def, ok := current.(*tracerProvider); ok {
			def.setDelegate(tp)
		}
	})
	globalTracer.Store(tracerProviderHolder{tp: tp})
}

func defaultTracerValue() *atomic.Value {
	v := &atomic.Value{}
	v.Store(tracerProviderHolder{tp: &tracerProvider{}})
	return v
}

图解

  ┌───────────────────┐
  │                   │
  │                   │
  │  tracer.Start()   ├──────────────────────────────┐
  │  tracer.Tracer()  │                              │
  │                   │                              │
  │                   │               6. 实 际 是 使 用 delegate 去 调 用 对 应 的 方 法
  ├───────────────────┘                              │
  │                                                  │          zrpc.MustNewServer()
  │  go-redis/gorm/x                                 │                 │
  │   tracer = otel.GetTracerProvider()──┐           │                 │
  │                                      │           │                 │
  │                                      │           │        4. 设 置 链 路 追 踪 服 务 提 供 者
                           2. get global default     │                 │
                                         │           │                 │
┌───────────package global─────────────  │ ───────┐  │                 ▼
│                                        ▼        │  │        otel.SetTracerProvider()
│           ┌───────────────TracerProvider()      │  │                 │ tp = 0x03
│           │                                     │  │                 │
│           │                                     │  │                 │
│           │                                     │  │                 ▼
│   3. return global default                      │  │      ┌─ global.SetTracerProvider()
│           │                                     │  │      │          │
│           ▼                                     │  │      │          │
│  ┌─►globalTracer tracerProviderHolder = 0x01    │  │      │  5. 修改当前全局默认
│  │    tp tracerProvider = 0x02         ┌────┐   │  │      │          │
│  │      delegate trace.TracerProvider =│nil │   │  │      │          ▼
│  │                                     │    │   │  │      │ globalTracer tracerProviderHolder = 0x04
│  │                                     │    │      │      │   tp tracerProvider = 0x03
│ 1. init global default                 │0x03│◄─────┘      │
│  │                                     └────┘             │
│  └──defaultTracerValue()                 ▲      │         │
│                                          │      │         │
└────────────────────────────────────────  │ ─────┘         │
                                           │                │
                                           │                │
                                           │                │
                                           │                │
                                           │                │
                                           │       5-1. 把 delegate 从 nil => 0x03
                                           │                │
                                           │                │
                                           └────────────────┘
  • 调试断点的值也能说明这一点

本作品采用知识共享署名 4.0 国际许可协议进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,可适当缩放并在引用处附上图片所在的文章链接。