Go语言--opentracing-go

关于opentracing的简单介绍

什么是opentracing?

OpenTracing的数据模型
opentracing中的跟踪链由span隐性的定义。可别的,跟踪链可以认为是span的有向无环图(DAG),spans之间的边称为References。
以下是一个跨度的例子图:
在这里插入图片描述
解析为时间轴可视化的图:
在这里插入图片描述
每个span封装以下的状态:
(1)操作名称
(2)开始时间戳
(3)完成时间戳
(4)一组零个或多个key:value跨度标签。 键必须是字符串。 值可以是字符串,布尔型或数字类型。
(5)一组零个或多个跨度日志,每个跨度日志本身就是与时间戳配对的key:value映射。 键必须是字符串,尽管值可以是任何类型。 并非所有OpenTracing实现都必须支持每种值类型。
(6)SpanContext(请参见下文)
(7)零个或多个因果相关的Span的引用(通过那些相关Span的SpanContext)
每个SpanContext封装以下状态
(1)引用跨过程span的不同process所需的任何OpenTracing实现依赖状态(例如,跟踪和跨度ID)
(2)Baggage Items,仅是k:v键值对,跨越process的边界

span之间的References

Span可以引用因果相关的零个或多个其他SpanContext。
Span可以引用因果相关的零个或多个其他SpanContext。 OpenTracing当前定义了两种类型的引用:ChildOf和FollowsFrom。 两种参考类型都专门为子Span和父Span之间的直接因果关系建模。 将来,OpenTracing可能还会支持具有非因果关系的Span的引用类型(例如,批处理在一起的Span,卡在同一队列中的Span等)。
ChildOf:
span可以是父亲span的子,在ChildOf的reference中父的span某些情况下依赖于子的span,以下所有内容将构成ChildOf关系:
(1)代表RPC的服务器端的Span可以是代表该RPC客户端的Span的ChildOf
(2)表示SQL插入的Span可以是代表ORM保存方法的Span的ChildOf
(3)许多同时执行(可能是分布式的)工作的Span可能全部是单个父Span的ChildOf,它合并了在截止期限内返回的所有子代的结果
这些对于作为父级ChildOf的子级来说都是有效的时序图。
在这里插入图片描述
FollowsFrom:
一些父Span完全不依赖于其子Span的结果。 在这些情况下,我们仅说因果关系上子Span从父Span跟随。 有许多不同的FollowsFrom参考子类别,在将来的OpenTracing版本中,它们可能会更正式地加以区分。
这些对于“FollowsFrom”的孩子来说都是有效的时序图。
在这里插入图片描述

The OpenTracing API

OpenTracing规范中包含三种关键且相互关联的类型:Tracer,Span和SpanContext。
粗略地说,每种行为在典型的编程语言中都成为一种“方法”,尽管由于类型重载等原因,它实际上可能是一组相关的同级方法。
--------------------------------------------------------------Tracer--------------------------------------------------
Tracer接口创造span并且了解如何将span跨process序列化和反序列化他们。Tracer接口具有以下功能。
1.开始一个新的span:
必要参数:
操作名称,例子:get_account
可选参数:
1.零个或多个对相关SpanContext的引用,如果可能的话,包括ChildOf和FollowsFrom引用类型的简写。
2.可选的显式开始时间戳记; 如果省略,则默认使用当前的walltime
3.零个或多个标签
返回已经启动(但尚未完成)的Span实例
2.将SpanContext注入载体
必要参数:
1.SpanContext实例
2.格式描述符(通常但不一定是字符串常量),它告诉Tracer实现如何在载波参数中对SpanContext进行编码
3.载体,其类型由格式决定。 Tracer实现将根据格式在此载体对象中对SpanContext进行编码。
3.从运营商中提取SpanContext
必要参数:
1.格式描述符(通常但不一定是字符串常量),它告诉Tracer实现如何从载波参数中解码SpanContext
2.载体,其类型由格式决定。 Tracer实现将根据格式从该载体对象解码SpanContext。
当通过Tracer启动新的Span时,返回适合用作参考的SpanContext实例。

注意:注射和提取所需的格式
注入和提取均依赖于可扩展的格式参数,该参数指示关联的“载体”的类型以及如何在该载体中编码SpanContext。 所有Tracer实现都必须支持以下所有格式。

1.文本映射(Text Map):任意字符串到字符串的映射,其中键和值的字符集不受限制
2.HTTP标头(Http Headers):具有适用于HTTP标头(例如RFC 7230)的键和值的字符串到字符串的映射。 在实践中,由于HTTP头的处理方式存在“多样性”,因此强烈建议Tracer实现使用有限的HTTP头密钥空间并保守地转义值。
3.二进制(二进制):表示SpanContext的(单个)任意二进制Blob
--------------------------------------------------------------Span--------------------------------------------------
可以做的事情:
1.检索跨度SpanContext
2.覆盖操作名称
3.完成跨度
4.设置跨度标签
5.记录结构化数据
6.Set a baggage item
7.Get a baggage item
----------------------------------------------------------SpanContext----------------------------------------------
SpanContext不仅仅是通用OpenTracing层上的有用功能,而是更多的“概念”。 也就是说,这对于OpenTracing实现至关重要,并且确实提供了自己的瘦API。 大多数OpenTracing用户在启动新的Span或向某些传输协议注入/从某些传输协议提取跟踪时,仅通过引用与SpanContext进行交互。

在OpenTracing中,我们强制SpanContext实例是不可变的,以避免围绕Span精加工和引用的复杂生命周期问题。

----------------------------------------------------------NoopTracer----------------------------------------------
所有OpenTracing语言API也必须提供某种NoopTracer实现,该实现可用于标记控制OpenTracing或注入对测试无害的东西(等等)。 在某些情况下(例如Java),NoopTracer可能位于其自己的打包工件中。

opentracing-go使用实例

初始化tracer

func TraceInit(serviceName string, samplerType string, samplerParam float64) (opentracing.Tracer, io.Closer) {
	cfg := &config.Configuration{
		ServiceName: serviceName,
		Sampler: &config.SamplerConfig{
			Type:  samplerType,
			Param: samplerParam,
		},
		Reporter: &config.ReporterConfig{
			LocalAgentHostPort: "127.0.0.1:6831",
			LogSpans:           true,
		},
	}

	tracer, closer, err := cfg.NewTracer(config.Logger(jaeger.StdLogger))
	if err != nil {
		panic(fmt.Sprintf("Init failed: %v\n", err))
	}

	return tracer,closer
}

客户端注入信息

  func makeSomeRequest(ctx context.Context) ... {
        if span := opentracing.SpanFromContext(ctx); span != nil {
            httpClient := &http.Client{}
            httpReq, _ := http.NewRequest("GET", "http://myservice/", nil)

            // Transmit the span's TraceContext as HTTP headers on our
            // outbound request.
            opentracing.GlobalTracer().Inject(
                span.Context(),
                opentracing.HTTPHeaders,
                opentracing.HTTPHeadersCarrier(httpReq.Header))

            resp, err := httpClient.Do(httpReq)
            ...
        }
        ...
    }

服务端解析信息

    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        var serverSpan opentracing.Span
        appSpecificOperationName := ...
        wireContext, err := opentracing.GlobalTracer().Extract(
            opentracing.HTTPHeaders,
            opentracing.HTTPHeadersCarrier(req.Header))
        if err != nil {
            // Optionally record something about err here
        }

        // Create the span referring to the RPC client if available.
        // If wireContext == nil, a root span will be created.
        serverSpan = opentracing.StartSpan(
            appSpecificOperationName,
            ext.RPCServerOption(wireContext))

        defer serverSpan.Finish()

        ctx := opentracing.ContextWithSpan(context.Background(), serverSpan)
        ...
    }

以下是简化他人的客户端代码

package main

import (
//	"context"
	"fmt"
	"github.com/uber/jaeger-client-go"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"

	"github.com/opentracing/opentracing-go"
//	"github.com/opentracing/opentracing-go/log"
	"github.com/uber/jaeger-client-go/config"
)

const (
	URL        = "http://localhost:8080"
	LIST_API   = "/getList"
	RESULT_API = "/getResult"
)

var (
	flag = make(chan bool)
)



func TraceInit(serviceName string, samplerType string, samplerParam float64) (opentracing.Tracer, io.Closer) {
	cfg := &config.Configuration{
		ServiceName: serviceName,
		Sampler: &config.SamplerConfig{
			Type:  samplerType,
			Param: samplerParam,
		},
		Reporter: &config.ReporterConfig{
			LocalAgentHostPort: "127.0.0.1:6831",
			LogSpans:           true,
		},
	}

	tracer, closer, err := cfg.NewTracer(config.Logger(jaeger.StdLogger))
	if err != nil {
		panic(fmt.Sprintf("Init failed: %v\n", err))
	}

	return tracer,closer
}


func sendRequest(req *http.Request) {
	go func(req *http.Request) {
		resp, err := http.DefaultClient.Do(req)

		if err != nil {
			fmt.Printf("Do send requst failed(%s)\n", err)
			return
		}

		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			fmt.Printf("ReadAll error(%s)\n", err)
			return
		}

		if resp.StatusCode != 200 {
			return
		}
		fmt.Printf("Response:%s\n", string(body))
		flag <- true
	}(req)
}

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Argument error(getlist or getresult number) ")
		os.Exit(1)
	}

	tracer, closer := TraceInit("CS-tracing", "const", 1)
	defer closer.Close()
	opentracing.SetGlobalTracer(tracer)
	span := tracer.StartSpan(fmt.Sprintf("%s trace", os.Args[1]))
	span.SetTag("trace to", os.Args[1])
	defer span.Finish()
	api := ""
	var err error

	if os.Args[1] == "getlist" {
		api = LIST_API
	}

	reqURL := URL + api
	req, err := http.NewRequest("GET", reqURL, nil)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	span.Tracer().Inject(
		span.Context(),
		opentracing.HTTPHeaders,
		opentracing.HTTPHeadersCarrier(req.Header),
	)

	if os.Args[1] == "getresult" {
		q := req.URL.Query()
		q.Add("num", os.Args[2])
		req.URL.RawQuery = q.Encode()
	}
	sendRequest(req)

	<-flag
}

对应的服务端代码

package main

import (
	"fmt"
	"github.com/uber/jaeger-client-go"
	"io"
	"net/http"

	"github.com/uber/jaeger-client-go/config"
	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/ext"
)

var (
	tracer opentracing.Tracer
)

func TraceInit(serviceName string, samplerType string, samplerParam float64) (opentracing.Tracer, io.Closer) {
	cfg := &config.Configuration{
		ServiceName: serviceName,
		Sampler: &config.SamplerConfig{
			Type:  samplerType,
			Param: samplerParam,
		},
		Reporter: &config.ReporterConfig{
			LocalAgentHostPort: "127.0.0.1:6831",
			LogSpans:           true,
		},
	}

	tracer, closer, err := cfg.NewTracer(config.Logger(jaeger.StdLogger))
	if err != nil {
		panic(fmt.Sprintf("Init failed: %v\n", err))
	}

	return tracer,closer
}


func GetListProc(w http.ResponseWriter, req *http.Request) {

	spanCtx, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
	span := tracer.StartSpan("GetListProc", ext.RPCServerOption(spanCtx))
	defer span.Finish()

	fmt.Println("Get request getList")
	respList := []string{"l1", "l2", "l3", "l4", "l5"}
	respString := ""

	for _, v := range respList {
		respString += v + ","
	}

	fmt.Println(respString)
	io.WriteString(w, respString)
}

func main() {
	var closer io.Closer
	tracer, closer = TraceInit("Trace-Server", "const", 1)
	defer closer.Close()

	http.HandleFunc("/getList", GetListProc)

	http.ListenAndServe(":8080", nil)
}

参考文件:
https://opentracing.io/specification/(概念)
https://github.com/opentracing/opentracing-go
https://segmentfault.com/a/1190000021273636?utm_source=tag-newest
https://blog.csdn.net/liyunlong41/article/details/88043604
https://blog.csdn.net/liyunlong41/article/details/87932953

发布了222 篇原创文章 · 获赞 35 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/hello_bravo_/article/details/104527245