打打闹闹 發表於 2022-6-7 14:27:00

Go微服务框架go-kratos实战学习05:分布式链路追踪 OpenTelemetry, jaeger 使用

<h2 id="一分布式链路追踪发展简介">一、分布式链路追踪发展简介</h2>
<h3 id="11-分布式链路追踪介绍">1.1 分布式链路追踪介绍</h3>
<p>关于分布式链路追踪的介绍,可以查看我前面的文章 微服务架构学习与思考(09):分布式链路追踪系统-dapper论文学习(https://www.cnblogs.com/jiujuan/p/16097314.html) 。</p>
<p>这里的 OpenTelemetry 有一段发展历程。</p>
<p>APM(Application Performance Monitoring) 和 Distributed Tracing(分布式跟踪),后者是前者的子集。</p>
<p>微服务架构流行起来后,为了监控和定位微服务中请求链路过长导致的定位和监控问题,分布链路监控也蓬勃发展起来。出现了</p>
<p>很多有名的产品,比如:Jaeger,Pinpoint,Zipkin,Skywalking 等等。这里有个问题,就是每家都有自己的一套数据采集标准和SDK。</p>
<p>为了统一这些标准,国外的人们就创建了 OpenTracing 和 OpenCensus 2 个标准。最先出现的是 OpenTracing。为了统一标准,后来两者合并为 OpenTelemetry。</p>
<h3 id="12-opentracing">1.2 OpenTracing</h3>
<p>OpenTracing 制定了一套与平台无关、厂商无关的协议标准,使得开发人员能够方便的添加或更换底层APM的实现。</p>
<p>它是 CNCF 的项目。OpenTracing 协议的产品有 Jaeger、Zipkin 等等。</p>
<p><strong>OpenTracing 数据模型</strong></p>
<ul>
<li><strong>Trace(s)</strong>:</li>
</ul>
<blockquote>
<p>Trace(s) 在 OpenTracing 中是被 spans 隐式定义的。一个 trace 可以被认为是由一个或多个 span 组成的有向无环图。</p>
<p>比如,下图示例就表示一个 trace 由 8 个 span 组成,也就是一次链路追踪由 8 个 span 组成:</p>
<pre><code class="language-shell">单个 trace(链路) 中 span 之间的关系


      ←←←(the root span)
            |
   +------+------+
   |             |
       ←←←(Span C is a `ChildOf` Span A)
   |             |
      +---+-------+
               |         |
             &gt;&gt;&gt; &gt;&gt;&gt;
                                       ↑
                                       ↑
                                       ↑
                         (Span G `FollowsFrom` Span F)
</code></pre>
</blockquote>
<p>用时间轴来可视化这次链路追踪图,更容易理解:</p>
<pre><code class="language-shell">Temporal relationships between Spans in a single Trace


––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–&gt; time


   
      
   
               
</code></pre>
<p>(来自:https://opentracing.io/specification/)</p>
<ul>
<li><strong>Span</strong>:</li>
</ul>
<blockquote>
<p>Span 是一次链路追踪里的基本组成元素,一个 Span 表示一个独立工作单元,比如一次 http 请求,一次函数调用等。每个 span 里元素:</p>
<ul>
<li>An operation name,服务/操作名称</li>
<li>A start timestamp,开始时间</li>
<li>A finish timestamp,结束时间</li>
<li>Span Tags,key:value 数据形式,用户自定义的标签,主要用途是链路记录信息的查询过滤。</li>
<li>Span Logs,key:value 数据形式,主要用途是记录某些事件和事件发生的时间。</li>
<li>SpanContext 看下面解释</li>
<li>References,对 0 或 更多个相关 span 的引用(通过 SpanContext 来引用)</li>
</ul>
</blockquote>
<ul>
<li><strong>SpanContext</strong>:</li>
</ul>
<blockquote>
<p>SpanContext 携带跨进程(跨服务)通信的数据。它的组成:</p>
<ul>
<li>在系统中表示 span 的信息。比如 span_id, trace_id。</li>
<li>Baggage Items,为整条追踪链路保存跨进程(跨服务)的数据,数据形式是 key:value</li>
</ul>
</blockquote>
<ul>
<li><strong>References</strong></li>
</ul>
<blockquote>
<p>多个 span 中的对应关系。OpenTracing 目前定义了 2 种关系:<code>ChildOf</code> 和 <code>FollowsFrom</code>:</p>
<ul>
<li><code>ChildOf</code>,一个子 span 可能是父 span 的 ChildOf</li>
</ul>
<pre><code class="language-shell">    [-Parent Span---------]
         [-Child Span----]

    [-Parent Span--------------]
         [-Child Span A----]
          [-Child Span B----]
      [-Child Span C----]
         [-Child Span D---------------]
         [-Child Span E----]
</code></pre>
<ul>
<li><code>FollowsFrom</code>,一些父 span 不依赖任何的子 span</li>
</ul>
<pre><code class="language-shell">    [-Parent Span-][-Child Span-]


    [-Parent Span--]
   [-Child Span-]


    [-Parent Span-]
                [-Child Span-]
</code></pre>
<p>(来自:https://opentracing.io/specification/)</p>
</blockquote>
<h3 id="13-opencensus">1.3 OpenCensus</h3>
<p>为什么又出现个 OpenCensus 这个项目?因为它有个好爹:google。要知道分布式跟踪的基础论文就是谷歌提出。</p>
<p>其实,刚开始它并不是要抢 OpenTracing 的饭碗,它只是为了把 Go 语言的 Metrics 采集、链路跟踪与 Go 语言自带的</p>
<p>profile 工具打通,统一用户的使用方式。但是随着项目发展,它也想把链路相关的统一一下。它不仅要做 Metrics 基础指标监控,</p>
<p>还要做 OpenTracing 的老本行:分布式跟踪。</p>
<h3 id="14-opentracing-与-opencensus-对比">1.4 OpenTracing 与 OpenCensus 对比</h3>
<p>2 者功能对比</p>
<p><img src="https://img2022.cnblogs.com/blog/650581/202206/650581-20220607142233155-1406665703.png" alt="image-20220605225353808" loading="lazy"></p>
<p><img src="https://img2022.cnblogs.com/blog/650581/202206/650581-20220607142233138-190669155.png" alt="image-20220605224745472" loading="lazy"></p>
<h3 id="15-opentelemetry">1.5 OpenTelemetry</h3>
<p>这样出现 2 个标准也不是个事啊,如是就出现了 OpenTelemetry,它把 2 者合并在一起了。</p>
<p>OpenTelemetry 的核心工作目前主要集中在 3 个部分:</p>
<blockquote>
<ol>
<li>规范的制定和协议的统一,规范包含数据传输、API 的规范,协议的统一包含:HTTP W3C 的标准支持及GRPC等框架的协议标准</li>
<li>多语言 SDK 的实现和集成,用户可以使用 SDK 进行代码自动注入和手动埋点,同时对其他三方库(Log4j、LogBack等)进行集成支持;</li>
<li>数据收集系统的实现,当前是基于 OpenCensus Service 的收集系统,包括 Agent 和 Collector。</li>
</ol>
</blockquote>
<p>(1.4 1.5来自: https://github.com/open-telemetry/docs-cn)</p>
<p>OpenTelemetry 的最终形态就是实现 Metrics、Tracing、Logging 的融合。</p>
<p>OpenTelemetry 整体架构图:</p>
<p><img src="https://img2022.cnblogs.com/blog/650581/202206/650581-20220607142233146-722885942.png" alt="image-20220606140340397" loading="lazy"></p>
<p>(来自:https://opentelemetry.io/docs/)</p>
<p>Tracing API 中几个重要概念:</p>
<blockquote>
<ul>
<li>TracerProvider:是 API 的入口点,提供了对 tracer 的访问。在代码里主要是创建一个 Tracer,一般是第三方分布式链路管理软件提供具体实现。默认是一个空的 TracerProvider(""),虽然也创建 Tracer,但是内部不会执行数据流传输逻辑。</li>
<li>Tracer:负责创建 span,一个 tracer 表示一次完整的追踪链路。tracer 由一个或多个 span 组成。跟上面的 OpenTracing 数据模型很像,所以说是两者合并。</li>
<li>Span:一次链路追踪操作里的基本操作元素。比如一次函数调用,一次 http 请求。</li>
</ul>
<p>里面还有很多详细介绍:https://opentelemetry.io/docs/reference/specification/trace/api/</p>
<p>还有一个数据采样,https://www.cnblogs.com/jiujuan/p/16097314.html - 前面学习 dapper 论文的这篇文章有介绍。</p>
</blockquote>
<p><strong>小结:</strong></p>
<p>一条链路追踪信息:</p>
<blockquote>
<p>有一条链路 trace,它是由一个或多个 span 组成, span 里会记录各种链路中的信息,跨进程的信息,各种 span 之间的关系。</p>
<p>使用哪种链路管理软件,则由 traceprovider 来设置。可以是 Jaeger,Pinpoint,Zipkin,Skywalking 等等。</p>
<p>span 中的信息收集到链路管理软件,然后可以用图来展示记录的链路信息和链路之间的关系。</p>
</blockquote>
<h2 id="二jaeger-简介">二、jaeger 简介</h2>
<p>Jaeger 是受到 Dapper 和 OpenZipkin 启发,是 Uber 开发的一款分布式链路追踪系统。</p>
<p>它用于监控微服务和排查微服务中出现的故障。</p>
<p><strong>jaeger 架构图</strong>:</p>
<p><img src="https://img2022.cnblogs.com/blog/650581/202206/650581-20220607142233132-511653015.png" alt="image-20220606204357332" loading="lazy"></p>
<p>(来自:https://www.jaegertracing.io/docs/1.35/architecture/)</p>
<p>jaeger 安装:</p>
<blockquote>
<p>参考我前面文章 :https://www.cnblogs.com/jiujuan/p/13235748.html docker all-in-one 安装</p>
</blockquote>
<h2 id="三kratos-中链路追踪使用代码">三、kratos 中链路追踪使用代码</h2>
<p>前面介绍了那么多,应该对 opentelemetry 大致有了一个了解。下面就在 kratos 中使用 opentelemetry。</p>
<p>这里使用 jaeger 作为链路追踪的管理软件。</p>
<blockquote>
<p>go 1.17</p>
<p>go-kratos 2.2.1</p>
<p>jaeger 1.35</p>
</blockquote>
<p>下面代码来自 go-kratos 官方例子。</p>
<h3 id="server-端">server 端</h3>
<p>在 main.go 中,有 grpc server 和 http server。</p>
<p>第一步,设置 TraceProvider()</p>
<pre><code class="language-go">// get trace provider
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
        }

        // New trace provider
        tp := tracesdk.NewTracerProvider(
                tracesdk.WithSampler(tracesdk.AlwaysSample()),
                // always be sure to batch in production
                tracesdk.WithBatcher(exp),
                // Record information about this application in an Resource.
                tracesdk.WithResource(
                        resource.NewWithAttributes(
                                semconv.SchemaURL,
                                semconv.ServiceNameKey.String(Name), // service name,实例名称
                                attribute.String("env", Env),      // environment
                                attribute.String("ID", Version),   // version
                        )),
        )
        return tp, nil
}
</code></pre>
<p>第二步,grpc server</p>
<pre><code class="language-go">url := "http://jaeger:14268/api/traces"
if os.Getenv("jaeger_url") != "" {
    url = os.Getenv("jeager_url")
}

tp, err := tracerProvider(url) // tracer provider
if err != nil {
    log.Error(err)
}

s := &amp;server{}

// grpc server
grpcSrv := grpc.NewServer(
    grpc.Address(":9000"),
    grpc.Middleware(
      middleware.Chain(
            recovery.Recovery(),
            tracing.Server(tracing.WithTracerProvider(tp)), //设置trace,传入 trace provider
            logging.Server(logger),
      ),
    ),
)

</code></pre>
<p>第三步,http server</p>
<pre><code class="language-go">func main() {
        logger := log.NewStdLogger(os.Stdout)
        log := log.NewHelper(logger)

        tp, err := tracerProvider("http://jaeger:14268/api/traces")
        if err != nil {
                log.Error(err)
        }

        httpSrv := http.NewServer(
                http.Address(":8080"),
                http.Middleware(
                        middleware.Chain(
                                recovery.Recovery(),
                                // Configuring tracing middleware
                                tracing.Server(
                                        tracing.WithTracerProvider(tp), // 提供 trace provider
                                ),
                                logging.Server(logger),
                        ),
                ),
        )
        s := &amp;server{}
        pb.RegisterUserHTTPServer(httpSrv, s)

        app := kratos.New(
                kratos.Name(Name),
                kratos.Server(
                        httpSrv,
                ),
        )

        if err := app.Run(); err != nil {
                log.Error(err)
        }
}
</code></pre>
<h3 id="client-端">client 端</h3>
<p>grpc client 和 http client</p>
<p>grpc client:</p>
<pre><code class="language-go">// create grpc conn
// only for demo, use single instance in production env
conn, err := grpc.DialInsecure(ctx,
   grpc.WithEndpoint("127.0.0.1:9000"),
   grpc.WithMiddleware(middleware.Chain(
           tracing.Client( //trace client
                   tracing.WithTracerProvider(s.tracer),
           ),
           recovery.Recovery(),
   )),
   grpc.WithTimeout(time.Second*2),
)
if err != nil {
    return nil, err
}
</code></pre>
<p>http client:</p>
<pre><code class="language-go">http.NewClient(ctx, http.WithMiddleware(
    tracing.Client(
      tracing.WithTracerProvider(s.tracer),
    ),
))
</code></pre>
<h2 id="四在student项目里使用链路追踪">四、在student项目里使用链路追踪</h2>
<p>在前面的 go-kratos gorm 练习项目中加入链路追踪。<br>
https://github.com/jiujuan/go-kratos-demos/tree/master/student。</p>
<h3 id="41-编写代码">4.1 编写代码</h3>
<p><strong>第一步</strong>,在 internal/server 下新建 pkg/tracer 文件夹,tracer.go 程序</p>
<p>把 tracer.go 作为一个独立文件</p>
<pre><code class="language-go">package tracer

import (
        "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.10.0"
)

type Conf struct {
        Name string
        Envstring
        Verstring
        Urlstring
}

func NewConf(name, env, ver, url string) *Conf {
        return &amp;Conf{
                Name: name,
                Env:env,
                Ver:ver,
                Url:url,
        }
}

func (c *Conf) TracerProvider() (*tracesdk.TracerProvider, error) {
        exp, err := jaeger.New(
                jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(c.Url)),
        )
        if err != nil {
                return nil, err
        }

        tp := tracesdk.NewTracerProvider(
                tracesdk.WithSampler(tracesdk.AlwaysSample()),
                tracesdk.WithBatcher(exp),
                tracesdk.WithResource(
                        resource.NewWithAttributes(
                                semconv.SchemaURL,
                                semconv.ServiceNameKey.String(c.Name),
                                attribute.String("env", c.Env),
                                attribute.String("ver", c.Ver),
                        )),
        )
        return tp, nil
}

</code></pre>
<p><strong>第二步</strong>,在 internal/server/grpc.go:NewGRPCServer() 函数加入链路追踪代码:</p>
<pre><code class="language-go">var opts = []grpc.ServerOption{
        grpc.Middleware(
                recovery.Recovery(),
                tracing.Server(), // 链路追踪
        ),
}
</code></pre>
<p><strong>第三步</strong>,在 internal/server/grpc.go:NewHTTPServer() 函数加入链路追踪代码:</p>
<pre><code class="language-go">var opts = []http.ServerOption{
        http.Middleware(
                recovery.Recovery(),
                tracing.Server(), // 链路追踪
        ),
}
</code></pre>
<p><strong>第四步</strong>,在 cmd/student/main.go 加入如下代码:</p>
<pre><code class="language-go">// 配置,启动链路追踪
url := "http://192.168.0.103:14268/api/traces"
Name = "kratos.service.student"
id = "kratos.id.student.1"
Version = "test-V0.0.1"
traceconf := tracer.NewConf(Name, id, Version, url)
tp, _ := traceconf.TracerProvider()
otel.SetTracerProvider(tp) // 为全局链路追踪
</code></pre>
<p>上面这段程序可以用 wire 配置程序。</p>
<p>完整代码地址:完整代码地址:https://github.com/jiujuan/go-kratos-demos/tree/master/student</p>
<h3 id="42-测试">4.2 测试</h3>
<p>请先自行安装 jaeger。</p>
<blockquote>
<p>可以用 docker all-in-one 快速安装,详细命令请参考:https://www.cnblogs.com/jiujuan/p/13235748.html</p>
</blockquote>
<p><strong>第一步</strong>,启动kratos服务</p>
<pre><code class="language-shell">$ cd cmd/student
$ kratos run
INFO msg=config loaded: config.yaml format: yaml
INFO msg= server listening on: [::]:9000
INFO msg= server listening on: 127.0.0.1:8080
</code></pre>
<p><strong>第二步</strong>,使用 curlie - https://github.com/rs/curlie 测试:</p>
<pre><code class="language-shell">$ curliehttp://127.0.0.1:8080/student/3
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 34

{
    "name": "jimmy",
    "status": 0,
    "id": 3
}
</code></pre>
<p><strong>第三步</strong>:打开 jaeger web ui 查看结果</p>
<p>http://192.168.0.103:16686/search</p>
<p><img src="https://img2022.cnblogs.com/blog/650581/202206/650581-20220612192722682-1457085856.png" alt="image-20220612191752291" loading="lazy"></p>
<p><img src="https://img2022.cnblogs.com/blog/650581/202206/650581-20220612192722636-363918660.png" alt="image-20220612191904418" loading="lazy"></p>
<blockquote>
<p>完整代码地址:完整代码地址:https://github.com/jiujuan/go-kratos-demos/tree/master/student</p>
</blockquote>
<hr>
<p>也欢迎到我的公众号:九卷技术录-kratos实战学习05:分布式链路追踪 交流</p>
<h2 id="五参考">五、参考</h2>
<ul>
<li>https://go-kratos.dev/docs/component/middleware/tracing/ 链路追踪</li>
<li>https://go-kratos.dev/blog/go-kratos-opentelemetry-practice/ 基于OpenTelemetry的链路追踪</li>
<li>https://opentracing.io/specification/ opentracing doc</li>
<li>https://opentelemetry.io/docs/instrumentation opentelemetry doc</li>
<li>https://opentelemetry.io/docs opentelemetry trace api</li>
<li>https://opencensus.io/ opencensus 官网</li>
<li>https://www.jaegertracing.io/docs/1.35/ jaeger doc</li>
</ul>


</div>
<div id="MySignature" role="contentinfo">
    == just do it ==<br><br>
来源:https://www.cnblogs.com/jiujuan/p/16349519.html
頁: [1]
查看完整版本: Go微服务框架go-kratos实战学习05:分布式链路追踪 OpenTelemetry, jaeger 使用