jeager的使用

jeager基本知识

数据格式说明

服务之间调用时,为了确保各自创建的span能串成一个调用链,那么服务之间需要传递trace和span相关信息。jaeger规范了信息的 格式,有提供默认方式,也支持自定义。

具体的说明,可以看官方文档,https://www.jaegertracing.io/docs/1.39/client-libraries/#propagation-format

对于客户端包用的,https://github.com/jaegertracing/jaeger-client-go, 里面也有使用说明。

1、trace/span 信息

数据的key为: uber-trace-id,

数据的value说明: {trace-id}:{span-id}:{parent-span-id}:{flags}

例子 5d936af3ff3599c6:2f5a9dee5abd7978:5d936af3ff3599c6:1

{trace_id}

  • 64位或者128位随机数字,用base16编码。
  • 值为0是无效数据

{span_id}

  • 64位随机数字,用base16编码。
  • 值为0是无效数据

{parent-span-id}

  • 64位随机数字,用base16编码。代表父span的id。
  • 值为0是有效的,代表当前是“根span”。

{flags}

  • 1个字节的位图。
  • 1代表这个trace被采样采纳。
  • 0代表这个trace不被采纳。
  • 2代表这个是“debug”标识。

2、Baggage

数据的key: uberctx-{baggage-key} 数据的value: {baggage-value} 字符串。

例子:

span.SetBaggageItem("key1", "value1")
span.SetBaggageItem("key2", "value2")

在http请求的header中能看到如下内容:

uberctx-key1: value1
uberctx-key2: value2

opentracing包

GitHub - opentracing/opentracing-go: OpenTracing API for Go. 🛑 This library is DEPRECATED! https://github.com/opentracing/specification/issues/163 包里面readme中有详细说明

如果A服务通过http调用B服务,A中发送的header要注入jaeger链路信息。

想要把jaeger链路信息发送到下一个服务,就要通过header中传递数据。我们只需要把本服务当前span拿到,把它的信息注入到要发送的header中即可,jaeger提供了tracer.Inject()方法,参数就是span的上下文,数据格式,存数据的header。

如下,从当前ctx中拿到span后,把span的context()注入搭配请求的header中。

    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)
            ...
        }
        ...
    }

B服务拿到http请求,要从header中取到数据。如下:

从header中抽取需要的数据,如果存在就用于创建span。

    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)
        ...
    }

启动一个服务

本地运行jaeger

docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411  \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 9411:9411 \
--restart=always \
jaegertracing/all-in-one:1.15

ui的访问方式:http://127.0.0.1:16686/search

gin框架项目demo_v2

1、jaeger初始化

在项目目录下,创建 jaeger/init.go ,里面提供初始化iaeger服务的方法 StartJaeger()

package jaeger

import (
	"fmt"
	"github.com/opentracing/opentracing-go"
	"github.com/uber/jaeger-client-go/config"
	"io"
	"io/ioutil"
	"log"
)


var JaegerUrl = "127.0.0.1"
var jaegerPort = 6831

func StartJaeger(serviceName string) {
	tracer, _ := initJaeger(serviceName)
	opentracing.SetGlobalTracer(tracer)
}

func initJaeger(serviceName string) (opentracing.Tracer, io.Closer) {
	if JaegerUrl == "" {
		return opentracing.NoopTracer{}, ioutil.NopCloser(nil)
	}

	cfg := &config.Configuration{
		ServiceName: serviceName,
		Sampler: &config.SamplerConfig{
			Type: "const",
			Param: 1,
		},
		Reporter: &config.ReporterConfig{
			LocalAgentHostPort: fmt.Sprintf("%s:%d", JaegerUrl, jaegerPort),
			LogSpans: true,
		},
	}

	tracer, closer, err := cfg.NewTracer()
	if err != nil {
		log.Fatalf("Failed to init Jaeger client: %s", err)
	}
	return tracer, closer
}

2、gin的middleware

在项目目录下,创建 jaeger/gin.go ,里面提gin框架用的middleware

它从header中抽取到链路追踪的context,然后基于它创建span。可能是一个新的span,也可能是和web调用方的链路连在一起的span。再把这个span的ctx放到gin.Context中给后续用。

package jaeger

import (
	"context"
	"github.com/gin-gonic/gin"
	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/ext"
	"net/http"
)

var (
	SpanCTX = "span-ctx"
)

func Middleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		tracer := opentracing.GlobalTracer()
		var span opentracing.Span

		// 从header中获取jaeger信息
		wireContext, err := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(c.Request.Header))
		// 如果失败,构建新的span
		if err != nil {
			span = opentracing.StartSpan(c.FullPath())
		} else {
			span = opentracing.StartSpan(c.FullPath(), ext.RPCServerOption(wireContext))
		}

		defer span.Finish()

		c.Set(SpanCTX, opentracing.ContextWithSpan(c, span))
		c.Next()
	}
}

 3、gin的server1例子

目录文件 v2/http_server1/main.go

  • 这是一个gin的web服务,监听8001端口,提供了一个“/hello”接口服务。
  • 浏览器访问8001的hello,服务会调用8002的服务。
  • 这个hello的handler中,它会从gin.Context中取到span的ctx,这个span的名称就是http收到路由,然后基于它创建span,名称随意,可以存一些信息等。
  • 调用8002服务之前,把span上下文取出来注入到http请求中。
package main

import (
	"context"
	"github.com/gin-gonic/gin"
	"github.com/opentracing/opentracing-go"
	"io/ioutil"
	"log"
	"net/http"
	jaeger2 "swwgo/basic/use_jaeger/v2/jaeger"
	"time"
)

func main() {
	jaeger2.StartJaeger("http_server1")

	r := gin.New()

	r.Use(jaeger2.Middleware())

	r.GET("/hello", func(c *gin.Context) {
		time.Sleep(time.Second * 1)
		// 再创建一个span
		spanC, _ := c.Get(jaeger2.SpanCTX)
		spanCtx := spanC.(context.Context)
		subSpan, _ := opentracing.StartSpanFromContext(spanCtx, "asdsad")
		defer subSpan.Finish()
		subSpan.SetBaggageItem("good_key1", "good_value1")
		subSpan.SetBaggageItem("good_key2", "good_value2")

		// 构建GET请求
		client := &http.Client{}
		request, _ := http.NewRequest("GET", "http://127.0.0.1:8002/hello", nil)

		// 把trace信息传到下一个http服务
		opentracing.GlobalTracer().Inject(subSpan.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(request.Header))

		resp, err := client.Do(request)

		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Println(err)
			return
		}

		c.JSON(200, gin.H{
			"code" : 0,
			"msg" : string(body),
		})
	})

	s := &http.Server{
		Addr: ":8001",
		Handler: r,
	}
	err := s.ListenAndServe()
	if err != nil {
		log.Panicf("start http failed: %v", err)
	}
}


4、gin的server2例子

目录文件 v2/http_server2/main.go

  • 这个server2比较简单,就是提供/hello接口提供服务,由middleware来构建追踪的链路。
package main

import (
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	jaeger2 "swwgo/basic/use_jaeger/v2/jaeger"
)

func main() {
	jaeger2.StartJaeger("http_server2")

	r := gin.New()

	r.Use(jaeger2.Middleware())

	r.GET("/hello", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"code" : 0,
			"msg" : "I am http-server2",
		})
	})

	s := &http.Server{
		Addr: ":8002",
		Handler: r,
	}
	err := s.ListenAndServe()
	if err != nil {
		log.Panicf("start http failed: %v", err)
	}
}


gin框架项目demo_v3

1、jaeger初始化

和demo_v2一样 jaeger/init.go内容一致。

2、gin的middleware

在项目目录下,创建 v3/jaeger/gin.go ,里面提gin框架用的middleware

package jaeger

import (
	"fmt"
	"github.com/gin-gonic/gin"
	kitot "github.com/go-kit/kit/tracing/opentracing"
	"github.com/opentracing/opentracing-go"
)

func Middleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 从header中拿到链路的spanCtx, 拿到则创建的span与之前链路打通的,否则是root span
		f := kitot.HTTPToContext(opentracing.GlobalTracer(), c.FullPath(), new(myLog))
		ctx := f(c, c.Request)

		// 拿到span,函数结束时调用finish
		span := opentracing.SpanFromContext(ctx)
		defer span.Finish()
		c.Request = c.Request.WithContext(ctx)

		c.Next()
	}
}

type myLog struct {}

func (l *myLog) Log(keyvals ...interface{}) error {
	fmt.Println(keyvals)
	return nil
}

上面用到了库github.com/go-kit/kit/tracing/opentracing中的方法,如下

它是从http的request中抽取到链路追踪的上下文信息,基于这个能构建出span数据,再把这个span的上下文输出出来,用于后续的span使用。

// HTTPToContext returns an http RequestFunc that tries to join with an
// OpenTracing trace found in `req` and starts a new Span called
// `operationName` accordingly. If no trace could be found in `req`, the Span
// will be a trace root. The Span is incorporated in the returned Context and
// can be retrieved with opentracing.SpanFromContext(ctx).
func HTTPToContext(tracer opentracing.Tracer, operationName string, logger log.Logger) kithttp.RequestFunc {
	return func(ctx context.Context, req *http.Request) context.Context {
		// Try to join to a trace propagated in `req`.
		var span opentracing.Span
		wireContext, err := tracer.Extract(
			opentracing.HTTPHeaders,
			opentracing.HTTPHeadersCarrier(req.Header),
		)
		if err != nil && err != opentracing.ErrSpanContextNotFound {
			logger.Log("err", err)
		}

		span = tracer.StartSpan(operationName, ext.RPCServerOption(wireContext))
		ext.HTTPMethod.Set(span, req.Method)
		ext.HTTPUrl.Set(span, req.URL.String())
		return opentracing.ContextWithSpan(ctx, span)
	}
}

 3、gin的server1例子

目录文件 v3/http_server1/main.go

package main

import (
	"github.com/gin-gonic/gin"
	"github.com/opentracing/opentracing-go"
	"io/ioutil"
	"log"
	"net/http"
	jaeger3 "swwgo/basic/use_jaeger/v3/jaeger"
	"time"
)

func main() {
	jaeger3.StartJaeger("http_server1")

	r := gin.New()

	r.Use(jaeger3.Middleware())

	r.GET("/hello", func(c *gin.Context) {
		time.Sleep(time.Second * 1)
		// 再创建一个span
		span, _ := opentracing.StartSpanFromContext(c.Request.Context(), "handler hello")
		defer span.Finish()
		span.SetBaggageItem("good_key1", "good_value1")
		span.SetBaggageItem("good_key2", "good_value2")

		// 构建GET请求
		client := &http.Client{}
		request, _ := http.NewRequest("GET", "http://127.0.0.1:8002/hello", nil)

		// 把trace信息传到下一个http服务
		opentracing.GlobalTracer().Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(request.Header))

		resp, err := client.Do(request)

		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Println(err)
			return
		}

		c.JSON(200, gin.H{
			"code" : 0,
			"msg" : string(body),
		})
	})

	s := &http.Server{
		Addr: ":8001",
		Handler: r,
	}
	err := s.ListenAndServe()
	if err != nil {
		log.Panicf("start http failed: %v", err)
	}
}


4、gin的server2例子

目录文件 v3/http_server2/main.go

package main

import (
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	jaeger3 "swwgo/basic/use_jaeger/v3/jaeger"
)

func main() {
	jaeger3.StartJaeger("http_server2")

	r := gin.New()

	r.Use(jaeger3.Middleware())

	r.GET("/hello", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"code" : 0,
			"msg" : "I am http-server2",
		})
	})

	s := &http.Server{
		Addr: ":8002",
		Handler: r,
	}
	err := s.ListenAndServe()
	if err != nil {
		log.Panicf("start http failed: %v", err)
	}
}


  • 16
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值