Jaeger 是一款开源的分布式跟踪系统,用于收集分布式应用程序的跟踪数据,可用于分析和故障排除。
Jaeger 使用分布式追踪(distributed tracing)来跟踪分布式应用程序中的请求。在分布式系统中,一次请求可能会在多个服务中处理,Jaeger 能够将整个请求的处理过程串联起来,形成一条链路追踪记录,从而让开发者可以清楚地了解每个请求的执行过程。
Jaeger 提供了丰富的跟踪数据,并能够将这些数据可视化展示,使得开发者可以更容易地发现和定位系统中的问题。Jaeger 还支持多种语言和平台,可与许多流行的开发框架和工具进行集成。
Jaeger 的核心组件包括:
除了以上核心组件,Jaeger 还提供了一些其他的工具和库,如用于不同编程语言的客户端库、用于集成不同平台的插件等。
总之,Jaeger 是一个非常强大的分布式跟踪系统,可以帮助开发者更好地了解分布式应用程序的执行情况,从而更容易地发现和解决问题。
分布式链路追踪(Distributed Tracing),也叫分布式链路跟踪,分布式跟踪,分布式追踪等等。本文使用分布式Trace来简称分布式链路追踪。
本篇文章只是从大致的角度来阐述什么是分布式Trace,以及一个分布式Trace系统具备哪些要点和特征。
场景:
先从几个场景来看为什么需要分布式Trace
开发A
编写了一段代码,代码依赖了很多的接口。一个调用下去没出结果,或者超时了,Debug
之后发现是接口M
挂了,然后找到这个接口M
的负责人B
,告知B
接口挂了。B
拉起自己的调用和Debug
环境,按照之前传过来的调用方式重新Debug
了一遍自己的接口,发现NND
是自己依赖的接口N
挂了,然后找到接口N
负责人C
。
C
同样Debug了自己的接口(此处省略一万个怎么可能呢,你调用参数不对吧’),最终发现是某个空判断错误,修复bug,转告给B说我们bug修复了,B再转告给A说,是C那个傻x弄挂了,现在Ok了,你试一下。
就这样,一个上午就没了,看着手头的需求越堆越高,内心是这样
哪一天系统完成了开发,需要进行性能测试,发现哪些地方调用比较慢,影响了全局。
A工程师拉起自己的系统,调用一遍,就汇报给老板,时间没啥问题。B工程师拉起自己的系统,调用了一遍,也没啥问题,同时将结果汇报了给老板。C工程师这时候发现自己的系统比较慢,debug
发现原来是自己依赖的接口慢了,于是找到接口负责人。。balabala,和场景1一样,弄好了。老板―一把这些都记录下来,满满的一本子。哪天改了个需求,又重新来一遍,劳民伤财。
这两种场景只是缩影,假设这时候有这样一种系统,
zipkin | jaeger | skywalking | |
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,MQ | udp/http | gRPC |
ui丰富程度 | 低 | 中 | 中 |
实现方式-代码-侵入性 | 拦截请求,侵入 | 拦截请求,侵入 | 字节码注入,无侵入 |
扩展性 | 高 | 高 | 中 |
trace查询 | 支持 | 支持 | 支持 |
性能损失 | 中 | 中 | 低 |
docker run \
--rm \
--name jaeger \
-p6831:6831/udp \
-p16686:16686 \
jaegertracing/all-in-one:latest
client library
和collector
解耦,为 client library
屏蔽了路由和发现 collector
的细节。jaeger-agent
发送来的数据,然后将数据写入后端存储。Collector
被设计成无状态的组件,因此您可以同时运行任意数量的 jaeger-collector
。cassandra
、elasticsearch
。trace
并通过 UI 进行展示。Query
是无状态的,您可以启动多个实例,把它们部署在 nginx 这样的负载均衡器后面。分布式追踪系统发展很快,种类繁多,但核心步骤一般有三个:代码埋点,数据存储、查询展示
OpenTracing是一种用于分布式系统中跟踪、监测和分析请求的工具。它提供了一组API和语义标准,使得开发人员可以编写跨系统和跨服务的跟踪代码,从而能够更加准确地诊断和优化性能问题。
下面是OpenTracing的一些主要语义标准:
总之,OpenTracing提供了一套标准化的API和语义标准,用于在分布式系统中跟踪和分析请求。开发人员可以使用这些标准来编写跟踪代码,以便更准确地诊断和优化性能问题。
服务器(虚拟机)启动jaeger
需要gin和grpc的基础
目录:
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
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()
}
通过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())
}
}
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:
get访问后调用grpc实现追踪:
目录:
修改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