【链路追踪】Jaeger基于go的「Gin」「gRPC」进行链路追踪


前言

Jaeger 是一款开源的分布式跟踪系统,用于收集分布式应用程序的跟踪数据,可用于分析和故障排除。

Jaeger 使用分布式追踪(distributed tracing)来跟踪分布式应用程序中的请求。在分布式系统中,一次请求可能会在多个服务中处理,Jaeger 能够将整个请求的处理过程串联起来,形成一条链路追踪记录,从而让开发者可以清楚地了解每个请求的执行过程。

Jaeger 提供了丰富的跟踪数据,并能够将这些数据可视化展示,使得开发者可以更容易地发现和定位系统中的问题。Jaeger 还支持多种语言和平台,可与许多流行的开发框架和工具进行集成。

Jaeger 的核心组件包括:

  1. Agent: 负责从应用程序中收集跟踪数据并将其发送到 Jaeger 的 Collector 中。
  2. Collector: 负责接收来自 Agent 的跟踪数据,并对其进行处理和存储。
  3. Query: 提供 Jaeger 的 Web UI 和查询 API,用于搜索和可视化跟踪数据。\
  4. Storage: 用于存储跟踪数据,支持多种存储后端,如 Cassandra、Elasticsearch 和 Kafka 等。

除了以上核心组件,Jaeger 还提供了一些其他的工具和库,如用于不同编程语言的客户端库、用于集成不同平台的插件等。

总之,Jaeger 是一个非常强大的分布式跟踪系统,可以帮助开发者更好地了解分布式应用程序的执行情况,从而更容易地发现和解决问题。


一、什么是链路追踪

分布式链路追踪(Distributed Tracing),也叫分布式链路跟踪,分布式跟踪,分布式追踪等等。本文使用分布式Trace来简称分布式链路追踪。

本篇文章只是从大致的角度来阐述什么是分布式Trace,以及一个分布式Trace系统具备哪些要点和特征。

场景:

先从几个场景来看为什么需要分布式Trace

1. 场景:

开发A编写了一段代码,代码依赖了很多的接口。一个调用下去没出结果,或者超时了,Debug之后发现是接口M挂了,然后找到这个接口M的负责人B,告知B接口挂了。B拉起自己的调用和Debug环境,按照之前传过来的调用方式重新Debug了一遍自己的接口,发现NND是自己依赖的接口N挂了,然后找到接口N负责人C
C同样Debug了自己的接口(此处省略一万个怎么可能呢,你调用参数不对吧’),最终发现是某个空判断错误,修复bug,转告给B说我们bug修复了,B再转告给A说,是C那个傻x弄挂了,现在Ok了,你试一下。
就这样,一个上午就没了,看着手头的需求越堆越高,内心是这样

2. 场景

哪一天系统完成了开发,需要进行性能测试,发现哪些地方调用比较慢,影响了全局。
A工程师拉起自己的系统,调用一遍,就汇报给老板,时间没啥问题。B工程师拉起自己的系统,调用了一遍,也没啥问题,同时将结果汇报了给老板。C工程师这时候发现自己的系统比较慢,debug发现原来是自己依赖的接口慢了,于是找到接口负责人。。balabala,和场景1一样,弄好了。老板―一把这些都记录下来,满满的一本子。哪天改了个需求,又重新来一遍,劳民伤财。

解决方案

这两种场景只是缩影,假设这时候有这样一种系统,


二、链路追踪选型

zipkinjaegerskywalking
OpenTracing兼容
客户端支持语言java,c#,go,php,python等java,c#,go,php,python等Java, .NET Core, NodeJS,PHP,python
存储ES,mysql,Cassandra,内存ES,kafka,Cassandra,内存ES,H2,mysql,TIDB,sharding,sphere
传输协议支持http,MQudp/httpgRPC
ui丰富程度
实现方式-代码-侵入性拦截请求,侵入 拦截请求,侵入 字节码注入,无侵入
扩展性
trace查询支持 支持 支持
性能损失

三、jaeger安装和架构

1. 安装

docker run \
 --rm \
 --name jaeger \
 -p6831:6831/udp \
 -p16686:16686 \
 jaegertracing/all-in-one:latest

2. 架构

在这里插入图片描述

3. Jaeger组成

  • Jaeger Client - 为不同语言实现了符合 OpenTracing 标准的 SDK。应用程序通过 API 写入数据,client library 把 trace 信息按照应用程序指定的采样策略传递给 jaeger-agent。
  • Agent - 它是一个监听在 UDP 端口上接收 span 数据的网络守护进程,它会将数据批量发送给
    collector。它被设计成一个基础组件,部署到所有的宿主机上。Agent 将 client librarycollector 解耦,为 client library 屏蔽了路由和发现 collector 的细节。
  • Collector - 接收 jaeger-agent 发送来的数据,然后将数据写入后端存储。Collector 被设计成无状态的组件,因此您可以同时运行任意数量的 jaeger-collector
  • Data Store - 后端存储被设计成一个可插拔的组件,支持将数据写入 cassandraelasticsearch
  • Query - 接收查询请求,然后从后端存储系统中检索 trace 并通过 UI 进行展示。Query 是无状态的,您可以启动多个实例,把它们部署在 nginx 这样的负载均衡器后面。

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

4. OpenTracing语义标准

OpenTracing是一种用于分布式系统中跟踪、监测和分析请求的工具。它提供了一组API和语义标准,使得开发人员可以编写跨系统和跨服务的跟踪代码,从而能够更加准确地诊断和优化性能问题。

下面是OpenTracing的一些主要语义标准:

  • Span:Span是OpenTracing的基本单元。它代表一个请求或操作的时间跨度。Span由一个唯一的标识符(Span ID)和一个可选的父Span ID组成。Span还包括开始时间、结束时间、Span名称和任意数量的Span标记。Span的实现通常会将Span ID、开始时间和Span名称输出到日志中,以便进行后续分析。
  • Trace:Trace由一系列相互关联的Span组成,代表了整个请求或操作的时间跨度。每个Trace都有一个唯一的标识符(Trace ID),可以用于在分布式系统中追踪请求。
  • Context:Context是OpenTracing中的一个概念,它代表了跨系统或跨服务的请求或操作的上下文。Context包括Trace ID和Span ID等信息,可以用于在分布式系统中追踪请求。
  • Tag:Tag是Span的一种元数据,用于记录与Span相关的键值对。Tag的使用有助于分析和归档跟踪数据,例如记录请求的URL、请求方法和HTTP状态码等信息。
  • Baggage:Baggage是OpenTracing的一个概念,用于在跨系统或跨服务的请求或操作中传递数据。Baggage包括键值对,可以在请求或操作的各个组件之间传递。Baggage的使用可以帮助开发人员在系统中追踪数据,并在请求或操作的不同组件之间共享信息。

总之,OpenTracing提供了一套标准化的API和语义标准,用于在分布式系统中跟踪和分析请求。开发人员可以使用这些标准来编写跟踪代码,以便更准确地诊断和优化性能问题。

链路追踪实例:
在这里插入图片描述

四、go集成jaeger——基于gin和grpc

服务器(虚拟机)启动jaeger
需要gin和grpc的基础

目录:

  • client
    • client.go
  • otgrpc
  • proto
    • helloworld.proto
    • helloworld.pb.go
  • server
    • server.go
  • main.go

proto文件:

syntax = "proto3";
//option go_package = ".;proto";
package proto;
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
  string name = 1;
}
message HelloReply {
  string message = 1;
}

otcrpc:

链接:https://pan.baidu.com/s/1D9MwOCShwzvy7_6KMXTNhg?pwd=1234
提取码:1234

1. go通过jaeger-client发送单个 多个span

main:

package main
import (
	"github.com/opentracing/opentracing-go"
	"github.com/uber/jaeger-client-go"
	jaegercfg "github.com/uber/jaeger-client-go/config"
	"time"
)
func main() {
	cfg := jaegercfg.Configuration{
		Sampler: &jaegercfg.SamplerConfig{
			Type:  jaeger.SamplerTypeConst,
			Param: 1, //全部采样
		},
		Reporter: &jaegercfg.ReporterConfig{
			//当span发送到服务器时要不要打日志
			LogSpans:           true,
			//IP:PORT
			LocalAgentHostPort: "192.168.10.130:6831",
		},
		ServiceName: "mxshop",
	}
	//生成链路
	tracer, closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger))
	if err != nil {
		return
	}
	defer closer.Close()
	//创建父span时的名称
	parentSpan := tracer.StartSpan("main")
	//opentracing.ChildOf:父span   传输使用的Context 很重要!
	span := tracer.StartSpan("funcA", opentracing.ChildOf(parentSpan.Context()))
	time.Sleep(time.Millisecond * 500) //业务逻辑
	span.Finish()
	//嵌套
	span2 := tracer.StartSpan("funcB", opentracing.ChildOf(parentSpan.Context()))
	time.Sleep(time.Millisecond * 1000)//业务逻辑
	span2.Finish()
	parentSpan.Finish()
}

在这里插入图片描述

2. go下通过grpc发送span消息

通过grpc的拦截器进行发送span
使用第三方otgrpc()发送
底层传输是通过Context进行的

proto:

syntax = "proto3";
//option go_package = ".;proto";
package proto;
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

client:

package main

import (
	"context"
	"fmt"
	"github.com/opentracing/opentracing-go"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"oldpackagetest/grpc_test/proto"
	"oldpackagetest/jaeger_test/otgrpc"
)

func main() {
	//拦截器
	//使用的是server端提供的tracer
	conn, err := grpc.Dial("127.0.0.1:50052", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())))
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	c := proto.NewGreeterClient(conn)
	r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"})
	if err != nil {
		panic(err)
	}
	fmt.Println(r.Message)
}

server端:

package main

import (
	"context"
	"github.com/opentracing/opentracing-go"
	"github.com/uber/jaeger-client-go"
	jaegercfg "github.com/uber/jaeger-client-go/config"
	"google.golang.org/grpc"
	"net"
	"oldpackagetest/grpc_test/proto"
	"oldpackagetest/jaeger_test/otgrpc"
)

type Server struct{}

func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply, error) {
	return &proto.HelloReply{
		Message: "hello" + request.Name,
	}, nil
}
func main() {
	cfg := jaegercfg.Configuration{
		Sampler: &jaegercfg.SamplerConfig{
			Type:  jaeger.SamplerTypeConst,
			Param: 1, //全部采样
		},
		Reporter: &jaegercfg.ReporterConfig{
			//当span发送到服务器时要不要打日志
			LogSpans:           true,
			LocalAgentHostPort: "192.168.10.130:6831",
		},
		ServiceName: "mxshop-2",
	}
	tracer, closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger))
	if err != nil {
		panic(err)
	}
	//设置tracer全局 节省到处传递tracer的过程
	opentracing.SetGlobalTracer(tracer)
	defer closer.Close()
	//添加拦截器
	g := grpc.NewServer(grpc.UnaryInterceptor(otgrpc.OpenTracingServerInterceptor(opentracing.GlobalTracer())))
	proto.RegisterGreeterServer(g, &Server{})
	lis, err := net.Listen("tcp", "0.0.0.0:50052")
	if err != nil {
		panic("失败:" + err.Error())
	}
	err = g.Serve(lis)
	if err != nil {
		panic("失败:" + err.Error())
	}
}

3. gin中添加拦截器实现jaeger注入

main.go

package main

import (
	"github.com/gin-gonic/gin"
	"github.com/uber/jaeger-client-go"
	jaegercfg "github.com/uber/jaeger-client-go/config"
	"net/http"
)

//拦截器
func Trace() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		//jaeger配置
		cfg := jaegercfg.Configuration{
			Sampler: &jaegercfg.SamplerConfig{
				Type:  jaeger.SamplerTypeConst,
				Param: 1, //全部采样
			},
			Reporter: &jaegercfg.ReporterConfig{
				//当span发送到服务器时要不要打日志
				LogSpans:           true,
				LocalAgentHostPort: "192.168.10.130:6831",
			},
			ServiceName: "gin",
		}
		//创建jaeger
		tracer, closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger))
		if err != nil {
			panic(err)
		}
		defer closer.Close()
		//最开始的span,以url开始
		startSpan := tracer.StartSpan(ctx.Request.URL.Path)
		defer startSpan.Finish()
		//ctx.Set("tracer", tracer)
		//ctx.Set("parentSpan", startSpan)
		ctx.Next()
	}
}

func main() {
	router := gin.Default()
	router.Use(Trace())
	router.GET("/jaeger", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "jaeger",
		})
	})
	_ = router.Run(":8083")
}

访问127.0.0.1:8083/jaeger后查看jaeger:
在这里插入图片描述

4. 修改grpc_opentracing源码实现gin和grpc追踪

get访问后调用grpc实现追踪:

目录:

  • otgrpc
  • proto
    • helloworld.proto
    • helloworld.pb.go
  • server
    • server.go
  • main.go

修改otgrpc源码后的文件:链接:https://pan.baidu.com/s/1SFtRErJQva1nVFwgLRfwYA?pwd=1234
提取码:1234

proto和server还是上面的那个

main.go:

package main

import (
	"context"
	"github.com/gin-gonic/gin"
	"github.com/opentracing/opentracing-go"
	"github.com/uber/jaeger-client-go"
	jaegercfg "github.com/uber/jaeger-client-go/config"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"net/http"
	"oldpackagetest/gin_jaeger_test/gin/otgrpc"
	"oldpackagetest/gin_jaeger_test/gin/proto"
)

//拦截器
func Trace() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		//jaeger配置
		cfg := jaegercfg.Configuration{
			Sampler: &jaegercfg.SamplerConfig{
				Type:  jaeger.SamplerTypeConst,
				Param: 1, //全部采样
			},
			Reporter: &jaegercfg.ReporterConfig{
				//当span发送到服务器时要不要打日志
				LogSpans:           true,
				LocalAgentHostPort: "192.168.10.130:6831",
			},
			ServiceName: "gin-grpc",
		}
		//创建jaeger
		tracer, closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger))
		if err != nil {
			panic(err)
		}
		defer closer.Close()
		//最开始的span,以url开始
		startSpan := tracer.StartSpan(ctx.Request.URL.Path)
		defer startSpan.Finish()
		//将tradcer和span存放到gin.context中
		ctx.Set("tracer", tracer)
		ctx.Set("parentSpan", startSpan)
		ctx.Next()
	}
}

func main() {
	router := gin.Default()
	//添加gin拦截器
	router.Use(Trace())
	router.GET("/jaeger", func(ctx *gin.Context) {
		//添加grpc拦截器
		conn, err := grpc.Dial("127.0.0.1:50052", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())))
		if err != nil {
			panic(err)
		}
		defer conn.Close()
		cc := proto.NewGreeterClient(conn)
		//将gin.context放到context里
		r, err := cc.SayHello(context.WithValue(context.Background(), "ginContext", ctx), &proto.HelloRequest{Name: "bobby"})
		ctx.JSON(http.StatusOK, gin.H{
			"msg": r.Message,
		})
	})
	_ = router.Run(":8083")
}

server:

package main

import (
	"context"
	"github.com/opentracing/opentracing-go"
	"github.com/uber/jaeger-client-go"
	jaegercfg "github.com/uber/jaeger-client-go/config"
	"google.golang.org/grpc"
	"net"
	"oldpackagetest/gin_jaeger_test/gin/otgrpc"
	"oldpackagetest/gin_jaeger_test/gin/proto"
)

type Server struct{}

func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply, error) {
	parentSpan := opentracing.SpanFromContext(ctx)

	one := opentracing.GlobalTracer().StartSpan("one", opentracing.ChildOf(parentSpan.Context()))
	time.Sleep(time.Second * 2) //业务逻辑
	one.Finish()

	two := opentracing.GlobalTracer().StartSpan("two", opentracing.ChildOf(parentSpan.Context()))
	time.Sleep(time.Second * 2) //业务逻辑
	two.Finish()

	three := opentracing.GlobalTracer().StartSpan("three", opentracing.ChildOf(parentSpan.Context()))
	time.Sleep(time.Second * 2) //业务逻辑
	three.Finish()

	four := opentracing.GlobalTracer().StartSpan("four", opentracing.ChildOf(parentSpan.Context()))
	time.Sleep(time.Second * 2) //业务逻辑
	four.Finish()

	return &proto.HelloReply{
		Message: "hello" + request.Name,
	}, nil
}
func main() {
	cfg := jaegercfg.Configuration{
		Sampler: &jaegercfg.SamplerConfig{
			Type:  jaeger.SamplerTypeConst,
			Param: 1, //全部采样
		},
		Reporter: &jaegercfg.ReporterConfig{
			//当span发送到服务器时要不要打日志
			LogSpans:           true,
			LocalAgentHostPort: "192.168.10.130:6831",
		},
		ServiceName: "gin-grpc",
	}
	tracer, closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger))
	if err != nil {
		panic(err)
	}
	//设置tracer全局 节省到处传递tracer的过程
	opentracing.SetGlobalTracer(tracer)
	defer closer.Close()
	//拦截器
	g := grpc.NewServer(grpc.UnaryInterceptor(otgrpc.OpenTracingServerInterceptor(opentracing.GlobalTracer())))
	proto.RegisterGreeterServer(g, &Server{})
	lis, err := net.Listen("tcp", "0.0.0.0:50052")
	if err != nil {
		panic("失败:" + err.Error())
	}
	err = g.Serve(lis)
	if err != nil {
		panic("失败:" + err.Error())
	}
}

运行server后运行gin开启路由后访问127.0.0.1:8083/jaeger

在这里插入图片描述
这样通过jaeger做分布式微服务的复杂链路也可以追踪到了

Jaeger链路追踪是一种用于监控和追踪分布式系统中请求的工具。它可以帮助开发人员跟踪请求在系统中的传递路径,并提供详细的性能指标和错误信息。Jaeger的安装可以参考官方地址\[1\]和相关教程\[2\]。Jaeger由多个组件组成,包括Agent、Collector、Query Service等\[2\]。 在使用Go语言进行Jaeger链路追踪时,可以通过Jaeger客户端发送单个或多个span来追踪请求\[3\]。同时,还可以使用grpc发送span消息\[3\]。在Gin框架中,可以通过添加拦截器实现Jaeger的注入\[3\]。此外,还可以修改grpc_opentracing源码来实现GingRPC的追踪\[3\]。 总结来说,Jaeger链路追踪是一种用于监控和追踪分布式系统中请求的工具,可以帮助开发人员跟踪请求的传递路径和性能指标。在Go语言中,可以使用Jaeger客户端发送span来实现链路追踪,并通过拦截器和修改源码来实现GingRPC的追踪。 #### 引用[.reference_title] - *1* [【链路追踪Jaeger基于go的「Gin」「gRPC进行链路追踪](https://blog.csdn.net/the_shy_faker/article/details/129044832)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [35、jaeger链路追踪](https://blog.csdn.net/qq23001186/article/details/126339369)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jzin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值