• 分布式链路追踪那点事——微服务总结(三)


    前言

    • 链路追踪目前在微服务中已经有了成熟的方案,其中包括代码侵入式的和非侵入式的,各有利弊,看具体的业务所需选择

    • 本文主要讲解的是侵入式的链路追踪,选择 OpenTelemetryjaeger 实现链路追踪

    • 其中具体的demo 代码实现选择python和go, 但是OpenTelemetry 官方文档提供了丰富的各种语言的SDK以及很多有趣的定制化需求

      https://opentelemetry.io/docs/instrumentation/

    opentracing

    • opentracing 为业界公认的链路追踪协议,目前开源的链路追踪项目都是遵循 opentracing 协议的

    OpenTracing数据模型

    • 该协议的数据模型包括Trace(链路) 和 Span, 一条链路由多个Span 组成

      可以将Span 理解成一次方法调用, 一个程序块的调用, 或者一次RPC/数据库访问.只要是一个具有完整时间周期的程序访问,都可以被认为是一个span

      下面的示例Trace就是由8个Span组成:

      1. 单个Trace中,span间的因果关系
      2. 
      3. 
      4. [Span A]  ←←←(the root span)
      5.             |
      6.      +------+------+
      7.      |             |
      8. [Span B]      [Span C] ←←←(Span C 是 Span A 的孩子节点, ChildOf)
      9.      |             |
      10. [Span D]      +---+-------+
      11.                |           |
      12. [Span E]    [Span F] >>> [Span G] >>> [Span H]
      13.                                        ↑
      14.                                        ↑
      15.                                        ↑
      16.                          (Span G 在 Span F 后被调用, FollowsFrom)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16

      有些时候,使用下面这种,基于时间轴的时序图可以更好的展现Trace(调用链):

      1. 单个Trace中,span间的时间关系
      2. 
      3. 
      4. ––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time
      5. 
      6. [Span A···················································]
      7. [Span B··············································]
      8. [Span D··········································]
      9. [Span C········································]
      10. [Span E·······]        [Span F··] [Span G··] [Span H··]
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      每个Span包含以下的状态:

      • An operation name,操作名称
      • A start timestamp,起始时间
      • A finish timestamp,结束时间
      • Span Tag,一组键值对构成的Span标签集合。键值对中,键必须为string,值可以是字符串,布尔,或者数字类型。
      • Span Log,一组span的日志集合。 每次log操作包含一个键值对,以及一个时间戳。 键值对中,键必须为string,值可以是任意类型。 但是需要注意,不是所有的支持OpenTracing的Tracer,都需要支持所有的值类型。
      • SpanContext,Span上下文对象 (下面会详细说明)
      • References(Span间关系),相关的零个或者多个Span(Span间通过SpanContext建立这种关系)

      每一个SpanContext包含以下状态:

      • 任何一个OpenTracing的实现,都需要将当前调用链的状态(例如:trace和span的id),依赖一个独特的Span去跨进程边界传输
      • Baggage Items,Trace的随行数据,是一个键值对集合,它存在于trace中,也需要跨进程边界传输
    Span间关系
    • 一个Span可以与一个或者多个SpanContexts存在因果关系。OpenTracing目前定义了两种关系:ChildOf(父子) 和 FollowsFrom(跟随)。这两种关系明确的给出了两个父子关系的Span的因果模型。 将来,OpenTracing可能提供非因果关系的span间关系。(例如:span被批量处理,span被阻塞在同一个队列中,等等)。

    • ChildOf 引用: 一个span可能是一个父级span的孩子,即"ChildOf"关系。在"ChildOf"引用关系下,父级span某种程度上取决于子span。下面这些情况会构成"ChildOf"关系:

      • 一个RPC调用的服务端的span,和RPC服务客户端的span构成ChildOf关系
      • 一个sql insert操作的span,和ORM的save方法的span构成ChildOf关系
      • 很多span可以并行工作(或者分布式工作)都可能是一个父级的span的子项,他会合并所有子span的执行结果,并在指定期限内返回
    • 下面都是合理的表述一个"ChildOf"关系的父子节点关系的时序图

      1.     [-Parent Span---------]
      2.          [-Child Span----]
      3. 
      4.     [-Parent Span--------------]
      5.          [-Child Span A----]
      6.           [-Child Span B----]
      7.         [-Child Span C----]
      8.          [-Child Span D---------------]
      9.          [-Child Span E----]
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      FollowsFrom 引用: 一些父级节点不以任何方式依赖他们子节点的执行结果,这种情况下,我们说这些子span和父span之间是"FollowsFrom"的因果关系。"FollowsFrom"关系可以被分为很多不同的子类型,未来版本的OpenTracing中将正式的区分这些类型

      下面都是合理的表述一个"FollowFrom"关系的父子节点关系的时序图。

      1.     [-Parent Span-]  [-Child Span-]
      2. 
      3. 
      4.     [-Parent Span--]
      5.      [-Child Span-]
      6. 
      7. 
      8.     [-Parent Span-]
      9.                 [-Child Span-]
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

    OpenTracing API

    • OpenTracing标准中有三个重要的相互关联的类型,分别是Tracer, SpanSpanContext
    Tracer
    • Tracer接口用来创建Span,以及处理如何处理Inject(serialize) 和 Extract (deserialize),用于跨进程边界传递。它具有如下官方能力:

      创建一个新Span

      必填参数

      • operation name, 操作名, 一个具有可读性的字符串,代表这个span所做的工作(例如:RPC方法名,方法名,或者一个大型计算中的某个阶段或子任务)。操作名应该是一个抽象、通用,明确、具有统计意义的名称。因此,"get_user" 作为操作名,比 "get_user/314159"更好。

      例如,假设一个获取账户信息的span会有如下可能的名称:

      操作名指导意见
      get太抽象
      get_account/792太明确
      get_account正确的操作名,关于account_id=792的信息应该使用Tag操作

      可选参数

      • 零个或者多个关联(references)的SpanContext,如果可能,同时快速指定关系类型,ChildOf 还是 FollowsFrom
      • 一个可选的显性传递的开始时间;如果忽略,当前时间被用作开始时间。
      • 零个或者多个tag。

      返回值,返回一个已经启动Span实例

      SpanContext上下文Inject(注入)到carrier

      必填参数:

      • **SpanContext**实例
      • format(格式化)描述,一般会是一个字符串常量,但不做强制要求。通过此描述,通知Tracer实现,如何对SpanContext进行编码放入到carrier中。
      • carrier,根据format确定。Tracer实现根据format声明的格式,将SpanContext序列化到carrier对象中。

      SpanContext上下文从carrier中Extract(提取)

      必填参数:

      • format(格式化)描述,一般会是一个字符串常量,但不做强制要求。通过此描述,通知Tracer实现,如何从carrier中解码SpanContext
      • carrier,根据format确定。Tracer实现根据format声明的格式,从carrier中解码SpanContext

      返回值,返回一个SpanContext实例,可以使用这个SpanContext实例,通过Tracer创建新的Span

      注意,对于Inject(注入)和Extract(提取),format是必须的。

      Inject(注入)和Extract(提取)依赖于可扩展的format参数。format参数规定了另一个参数"carrier"的类型,同时约束了"carrier"中SpanContext是如何编码的。所有的Tracer实现,都必须支持下面的format。

      • Text Map: 基于字符串:字符串的map,对于key和value不约束字符集。
      • HTTP Headers: 适合作为HTTP头信息的,基于字符串:字符串的map。(RFC 7230.在工程实践中,如何处理HTTP头具有多样性,强烈建议tracer的使用者谨慎使用HTTP头的键值空间和转义符)
      • Binary: 一个简单的二进制大对象,记录SpanContext的信息。
    Span
    • Span结束后(span.finish()),除了通过Span获取SpanContext外,下列其他所有方法都不允许被调用。

    通过Span获取SpanContext

    不需要任何参数

    返回值:Span构建时传入的SpanContext。这个返回值在Span结束后(span.finish()),依然可以使用。

    复写操作名(operation name)

    必填参数

    • 新的操作名operation name,覆盖构建Span时,传入的操作名。

    结束Span

    可选参数

    • 一个明确的完成时间;如果省略此参数,使用当前时间作为完成时间。

    Span设置tag

    必填参数

    • tag key,必须是string类型
    • tag value,类型为字符串,布尔或者数字

    注意,OpenTracing标准包含**“standard tags,标准Tag”**,此文档中定义了Tag的标准含义。

    Log结构化数据

    必填参数

    • 一个或者多个键值对,其中键必须是字符串类型,值可以是任意类型。某些OpenTracing实现,可能支持更多的log值类型。

    可选参数

    • 一个明确的时间戳。如果指定时间戳,那么它必须在span的开始和结束时间之内。

    注意,OpenTracing标准包含**“standard log keys,标准log的键”**,此文档中定义了这些键的标准含义。

    设置一个baggage(随行数据)元素

    Baggage元素是一个键值对集合,将这些值设置给给定的SpanSpanSpanContext,以及所有和此Span有直接或者间接关系的本地Span。 也就是说,baggage元素随trace一起保持在带内传递。(带内传递,在这里指,随应用程序调用过程一起传递)

    Baggage元素为OpenTracing的实现全栈集成,提供了强大的功能 (例如:任意的应用程序数据,可以在移动端创建它,显然的,它会一直传递了系统最底层的存储系统。由于它如此强大的功能,他也会产生巨大的开销,请小心使用此特性。

    再次强调,请谨慎使用此特性。每一个键值都会被拷贝到每一个本地和远程的下级相关的span中,因此,总体上,他会有明显的网络和CPU开销。

    必填参数

    • baggage key, 字符串类型
    • baggage value, 字符串类型

    获取一个baggage元素

    必填参数

    • baggage key, 字符串类型

    返回值,相应的baggage value,或者可以标识元素值不存在的返回值(如Null)。

    SpanContext
    • 相对于OpenTracing中其他的功能,SpanContext更多的是一个“概念”。也就是说,OpenTracing实现中,需要重点考虑,并提供一套自己的API。 OpenTracing的使用者仅仅需要,在创建span、向传输协议Inject(注入)和从传输协议中Extract(提取)时,使用SpanContextreferences

      OpenTracing要求,SpanContext是不可变的,目的是防止由于Span的结束和相互关系,造成的复杂生命周期问题。

    遍历所有的baggage元素

    遍历模型依赖于语言,实现方式可能不一致。在语义上,要求调用者可以通过给定的SpanContext实例,高效的遍历所有的baggage元素

    NoopTracer

    所有的OpenTracing API实现,必须提供某种方式的NoopTracer实现。NoopTracer可以被用作控制或者测试时,进行无害的inject注入(等等)。例如,在 OpenTracing-Java实现中,NoopTracer在他自己的模块中。

    可选 API 元素

    有些语言的OpenTracing实现,为了在串行处理中,传递活跃的SpanSpanContext,提供了一些工具类。例如,opentracing-go中,通过context.Context机制,可以设置和获取活跃的Span

    jaeger 安装与简介

    jaeger 是用go 语言开源的一款链路追踪项目,提供了多种语言的SDK

    docker-compose yaml 文件:

    version: '2'
    services:
      jaeger:
        image: jaegertracing/all-in-one:1.37
        environment:
          - COLLECTOR_ZIPKIN_HOST_PORT=:9411
          - COLLECTOR_OTLP_ENABLED=true
        ports:
          - "5775:5775/udp"
          - "6831:6831/udp"
          - "16686:16686"
          - "6832:6832/udp"
          - "5778:5778"
          - "4317:4317"
          - "4318:4318"
          - "14268:14268"
          - "14250:14250"
          - "9411:9411"
        networks:
          - jaeger-example
    networks:
      jaeger-example:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    官方demo github 地址:

    https://github.com/jaegertracing/jaeger/tree/main/examples/hotrod

    架构说明:

    https://www.jaegertracing.io/docs/1.21/architecture/

    img

    Jaeger Client - 为不同语言实现了符合 OpenTracing 标准的 SDK。应用程序通过 API 写入数据,client library 把 trace 信息按照应用程序指定的采样策略传递给 jaeger-agent。

    Agent - 它是一个监听在 UDP 端口上接收 span 数据的网络守护进程,它会将数据批量发送给 collector。它被设计成一个基础组件,部署到所有的宿主机上。Agent 将 client library 和 collector 解耦,为 client library 屏蔽了路由和发现 collector 的细节。

    Collector - 接收 jaeger-agent 发送来的数据,然后将数据写入后端存储。Collector 被设计成无状态的组件,因此您可以同时运行任意数量的 jaeger-collector。

    Data Store - 后端存储被设计成一个可插拔的组件,支持将数据写入 cassandra、elastic search。

    Query - 接收查询请求,然后从后端存储系统中检索 trace 并通过 UI 进行展示。Query 是无状态的,您可以启动多个实例,把它们部署在 nginx 这样的负载均衡器后面。

    分布式追踪系统发展很快,种类繁多,但核心步骤一般有三个:代码埋点,数据存储、查询展示

    OpenTelemetry

    简介

    OpenTelemetry 的官方定位就是将分布式系统可观测,其遵守了openTracingOpenCensus 协议,将各个服务的追踪日志聚合

    img

    和 jaeger 一样 openTelemetry 的设计也非常灵活, 提供了多种语言的SDK, 使用简单。

    并且 jaeger 可以非常好的与 openTelemetry 交互,可以使用 openTelemetry 来搜集各个服务的代码埋点日志,然后将其发送至 jaeger-agent,这样就可以与 jaeger 的 强大生态共存。

    官方使用SDK文档

    https://opentelemetry.io/docs/instrumentation/

    jaeger 与 openTelemetry 简单交互

    这里只是摘抄了官方最简单的使用方式体验下其强大之处,更多复杂的使用建议参考上边的 opentelemetry 官方文档

    简单使用demo

    Python 服务代码接入

    from opentelemetry import trace
    from opentelemetry.exporter.jaeger.thrift import JaegerExporter
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.trace.export import BatchSpanProcessor
    from opentelemetry.sdk.resources import SERVICE_NAME, Resource
    
    resource = Resource(attributes={
        SERVICE_NAME: "your-service-name"
    })
    
    
    # 为 jaeger 部署的服务地址
    jaeger_exporter = JaegerExporter(
        agent_host_name="192.168.146.189",
        agent_port=6831,
    )
    
    provider = TracerProvider(resource=resource)
    processor = BatchSpanProcessor(jaeger_exporter)
    provider.add_span_processor(processor)
    trace.set_tracer_provider(provider)
    
    
    tracer = trace.get_tracer(__name__)
    with tracer.start_as_current_span("print") as span:
        print("foo")
        span.set_attribute("printed_string", "foo")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    Go 服务代码接入

    package main
    
    import (
        "context"
        "log"
        "time"
    
        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/attribute"
        "go.opentelemetry.io/otel/exporters/jaeger"
        "go.opentelemetry.io/otel/sdk/resource"
        tracesdk "go.opentelemetry.io/otel/sdk/trace"
        semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
    )
    
    const (
        service     = "trace-demo"
        environment = "production"
        id          = 1
    )
    
    // tracerProvider returns an OpenTelemetry TracerProvider configured to use
    // the Jaeger exporter that will send spans to the provided url. The returned
    // TracerProvider will also use a Resource configured with all the information
    // about the application.
    func tracerProvider(url string) (*tracesdk.TracerProvider, error) {
        // Create the Jaeger exporter
        exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
        if err != nil {
            return nil, err
        }
        tp := tracesdk.NewTracerProvider(
            // Always be sure to batch in production.
            tracesdk.WithBatcher(exp),
            // Record information about this application in a Resource.
            tracesdk.WithResource(resource.NewWithAttributes(
                semconv.SchemaURL,
                semconv.ServiceNameKey.String(service),
                attribute.String("environment", environment),
                attribute.Int64("ID", id),
            )),
        )
        return tp, nil
    }
    
    func main() {
        tp, err := tracerProvider("http://192.168.146.189:14268/api/traces")
        if err != nil {
            log.Fatal(err)
        }
    
        // Register our TracerProvider as the global so any imported
        // instrumentation in the future will default to using it.
        otel.SetTracerProvider(tp)
    
        ctx, cancel := context.WithCancel(context.Background())
        defer cancel()
    
        // Cleanly shutdown and flush telemetry when the application exits.
        defer func(ctx context.Context) {
            // Do not make the application hang when it is shutdown.
            ctx, cancel = context.WithTimeout(ctx, time.Second*5)
            defer cancel()
            if err := tp.Shutdown(ctx); err != nil {
                log.Fatal(err)
            }
        }(ctx)
    
        tr := tp.Tracer("component-main")
    
        ctx, span := tr.Start(ctx, "foo")
        defer span.End()
    
        bar(ctx)
    }
    
    func bar(ctx context.Context) {
        // Use the global TracerProvider.
        tr := otel.Tracer("component-bar")
        _, span := tr.Start(ctx, "bar")
        span.SetAttributes(attribute.Key("testset").String("value"))
        defer span.End()
    
        // Do bar...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    img

    gin 集成 opentelemetry 与 jaeger

    • gin 只需要在主线程中实例化一个jaeger的tracer provider 即可
    • 然后使用一个链路追踪的中间件即可,该中间件可以自己实现也可以使用 opentelemetry 官方的中间件

    官方的gin 链路追踪中间件:otelgin

    import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"	
    
    • 1

    ​ 自定义gin 中间件,自定义中间件的好处就是灵活,可以定制化对应的业务逻辑:

    package middlewares
    
    import (
    	"fmt"
    	"github.com/opentracing/opentracing-go"
    
    	"github.com/gin-gonic/gin"
    	"github.com/uber/jaeger-client-go"
    	jaegercfg "github.com/uber/jaeger-client-go/config"
    	
    	"micro/order-web/global"	// 全局配置文件
    )
    
    func Trace() gin.HandlerFunc {
    	return func(ctx *gin.Context) {
    		cfg := jaegercfg.Configuration{
    			Sampler: &jaegercfg.SamplerConfig{
    				Type:  jaeger.SamplerTypeConst,
    				Param: 1,
    			},
    			Reporter: &jaegercfg.ReporterConfig{
    				LogSpans:           true,
    				LocalAgentHostPort: fmt.Sprintf("%s:%d", global.ServerConfig.JaegerInfo.Host, global.ServerConfig.JaegerInfo.Port),
    			},
    			ServiceName: global.ServerConfig.JaegerInfo.Name,
    		}
    		tracer, closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger))
    		if err != nil {
    			panic(err)
    		}
    		opentracing.SetGlobalTracer(tracer)
    		defer closer.Close()
    
    		startSpan := tracer.StartSpan(ctx.Request.URL.Path)
    		defer startSpan.Finish()
    
    		ctx.Set("tracer", tracer)
    		ctx.Set("parentSpan", startSpan)
    		ctx.Next()
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 然后再handler 中就可以从context 中实例化对应span

    完整代码

    package main
    
    import (
    	"context"
    	"log"
    	"net/http"
    
    	"github.com/gin-gonic/gin"
    	"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
    	"go.opentelemetry.io/otel"
    	"go.opentelemetry.io/otel/attribute"
    	"go.opentelemetry.io/otel/codes"
    	"go.opentelemetry.io/otel/exporters/jaeger"
    	"go.opentelemetry.io/otel/propagation"
    	"go.opentelemetry.io/otel/sdk/resource"
    	sdktrace "go.opentelemetry.io/otel/sdk/trace"
    	semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
    	oteltrace "go.opentelemetry.io/otel/trace"
    )
    
    const (
    	service     = "gin-trace-demo"
    	environment = "production"
    	id          = 1
    )
    
    var tracer = otel.Tracer(service)
    
    type UserInfo struct {
    	Id   string `json:"id"`
    	Name string `json:"name"`
    }
    
    func GetUsers(c *gin.Context) {
    	id := c.Param("id")
    	// 实例化开始span: 从ctx 中获取span
    	// 可以在实例化span 的时候就设置一些属性
    	ctx, span := tracer.Start(c.Request.Context(), "getUser", oteltrace.WithAttributes(attribute.String("id", id)))
    	defer span.End()
    	// 模拟执行获取用户信息的业务流程
    	name := getUser(ctx, id)
    	userInfo := UserInfo{
    		Id:   id,
    		Name: name,
    	}
    	c.JSON(http.StatusOK, userInfo)
    }
    
    func getUser(ctx context.Context, id string) string {
    	// 实例化子span
    
    	_, childSpan := tracer.Start(ctx, "getUserSubProcess")
    	defer childSpan.End()
    	// 也可以在实例化后设置span的一些属性
    	var name string
    	if id == "123" {
    		name = "zhouzy1"
    	}
    	childSpan.SetAttributes(attribute.String("name", name))
    	childSpan.SetStatus(codes.Ok, "get userInfo success")
    	return name
    }
    
    func main() {
    	tp, err := initTracer()
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer func() {
    		if err := tp.Shutdown(context.Background()); err != nil {
    			log.Printf("Error shutting down tracer provider: %v", err)
    		}
    	}()
    	r := gin.New()
    	r.Use(otelgin.Middleware("my-server"))
    
    	r.GET("/users/:id", GetUsers)
    	_ = r.Run(":8080")
    }
    
    func initTracer() (*sdktrace.TracerProvider, error) {
    	// 使用jaeger 作为exporter
    	exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://192.168.146.189:14268/api/traces")))
    	if err != nil {
    		return nil, err
    	}
    	// 初始化 tracer provider
    	tp := sdktrace.NewTracerProvider(
    		sdktrace.WithBatcher(exporter),
    		sdktrace.WithResource(resource.NewWithAttributes(
    			semconv.SchemaURL,
    			semconv.ServiceNameKey.String(service),
    			attribute.String("environment", environment),
    			attribute.Int64("ID", id),
    		)),
    	)
    	otel.SetTracerProvider(tp)
    	otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
    	return tp, nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    img

    总结

    • 目前分布式微服务的链路追踪在Trace 上已经比较成熟,但是在log 和 metric 方面还处于测试阶段
    • 微服务已经成为了当今的主流,服务越来越多,服务之间的调用也越来越复杂,即使在每个服务中都尽量多的打印日志,但是在出现问题时,排查起来也比较耗时耗力
    • 如何做到服务可观测性非常的重要
  • 相关阅读:
    制作一个简单HTML传统端午节日网页(HTML+CSS)
    JavaScript规范
    Python之作业(三)
    Golang 减小可执行文件大小
    Selenium之入门
    大工22春《施工组织课程设计》离线作业模板及要求【标准答案】
    11 redis中分布式锁的实现
    swift-基础
    【数据结构】栈详解
    MongoDB聚合运算符:$lte
  • 原文地址:https://blog.csdn.net/qq_42586468/article/details/126560986