opentracing的官方文档
- https://opentracing.io/docs/getting-started/
- https://opentracing.io/docs/overview/inject-extract/
- Inject and Extract
关于inject和extract
说下 inject和extract 在一个trace中任何SpanContext都可以被注入到一个叫做Carrier的东西里面。 Carrier是一个进程之间沟通的数据结构或者数据接口,它携带trace的状态信息从一个进程传到另一个。
注入(Inject):
- 以内置的HTTP_HEADERS的carrier格式做例子说明, 它是用一个map,我们把SpanContext注入进去。
- 一个协议传输时要带上carrier内容,比如http协议,我们把carrier作为header发送。
抽取(Extract):
- 以内置的HTTP_HEADERS的carrier格式做例子说明,把从协议中拿到的关键数据是一个map。
- 从map中可以抽取出SpanContext信息,然后它就可以作为创建子span的上下文。
安装开发环境
启动开发环境的jeager
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
win10环境下,可以在本地gitbash命令行或者ubuntu子系统命令行中运行上面命令,就启动了容器。基于docker desktop工具,可以看到启动的容器,并方便地启停。
访问 http://127.0.0.1:16686/search
jaeger如何确保http过来的链路连续?
todo
jaeger如果确保grpc client发送链路数据?
1、我自己摸索时的实现。
一般程序比如gin框架的,会写个middleware,把span存到context中。后面的函数调用只要从context中拿到span就能创建子span, 即一般程序中context都放span从而能形成span链路。
在调用grpc的client之前,需要从自己程序中的context中拿到span数据,创建一个子span,然后把数据注入到grpc要传输的md中,grpc发送的context中带上md。
对于gin框架,在最外层有middleware,会把包含span信息的context放到gin.Context中,存为key是SpanCTX{}的空结构体,所以从gin.Context能拿到含span的上下文。
// 调用grpc,为了链路追踪,需要传context
sendCtx := context.Background()
v, has := c.Get(jaeger.SpanCTX)
if has {
if ctx, ok := v.(context.Context); ok {
// jaeger,传输trace/span信息
sendCtx = ctx
subSpan, _ := opentracing.StartSpanFromContext(sendCtx, "calc auth service")
m := map[string]string{}
opentracing.GlobalTracer().Inject(subSpan.Context(), opentracing.TextMap, opentracing.TextMapCarrier(m))
md := metadata.New(m)
sendCtx = metadata.NewOutgoingContext(ctx, md)
}
}
// 调用client带上ctx
identify, err := rpcclient.GetAuthGrpcClient().Identify(sendCtx, &opencar.Token{
Value: token,
})
2、opentracing提供了实现
在opentracing项目中,github.com\go-kit\kit\tracing\opentracing\grpc.go, 里面实现了几个函数,我们可以用ContextToGRPC(), 它从context中拿到span上下文写到grpc的md中。
ContextToGRPC
- 从context中拿span,没拿到就直接返回ctx
- 拿到了,就把里面span的context注入到md
- 也就是调用的md中已经被写入了数据,用它作为grpc的md即可
// ContextToGRPC returns a grpc RequestFunc that injects an OpenTracing Span
// found in `ctx` into the grpc Metadata. If no such Span can be found, the
// RequestFunc is a noop.
func ContextToGRPC(tracer opentracing.Tracer, logger log.Logger) func(ctx context.Context, md *metadata.MD) context.Context {
return func(ctx context.Context, md *metadata.MD) context.Context {
if span := opentracing.SpanFromContext(ctx); span != nil {
// There's nothing we can do with an error here.
if err := tracer.Inject(span.Context(), opentracing.TextMap, metadataReaderWriter{md}); err != nil {
logger.Log("err", err)
}
}
return ctx
}
}
jaeger如果确保grpc sever拿到链路数据?
1、我自己摸索时的实现。
当时并不知道extract怎么从md直接抽数据,所以自己实现把map[string][]string转为map[string]string
getJaeger := func(ctx context.Context, md metadata.MD) context.Context {
// 从md中获取, 转为map[string]string{}
m := map[string]string{}
for k, v := range md {
if len(v) > 0 {
m[k] = v[0]
}
}
// 抽取出jaeger信息转为
spanContext, err := tracer.Extract(opentracing.TextMap, opentracing.TextMapCarrier(m))
if err != nil {
return ctx
}
span := opentracing.StartSpan("rpc", ext.RPCServerOption(spanContext))
ctx = opentracing.ContextWithSpan(ctx, span)
return ctx
}
2、opentracing提供了实现
在opentracing项目中,github.com/go-kit/kit/tracing/opentracing/grpc.go, 里面实现了几个函数,用于从grpc的md中提取数据生成包含span的context,还有把context信息写入grpc要传递的md中。
GRPCToContext函数
- 通过trace的Extract从grpc的md中拿到spanContext数据。
- 基于这个spanContext创建一个span。
- 把这个span填入上下文context中,这样后续的业务处理函数从context就能拿到。
-
如果能拿到spanContext,span就串联上了链路信息了。
// GRPCToContext returns a grpc 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 GRPCToContext(tracer opentracing.Tracer, operationName string, logger log.Logger) func(ctx context.Context, md metadata.MD) context.Context {
return func(ctx context.Context, md metadata.MD) context.Context {
var span opentracing.Span
wireContext, err := tracer.Extract(opentracing.TextMap, metadataReaderWriter{&md})
if err != nil && err != opentracing.ErrSpanContextNotFound {
logger.Log("err", err)
}
span = tracer.StartSpan(operationName, ext.RPCServerOption(wireContext))
return opentracing.ContextWithSpan(ctx, span)
}
}
metadataReaderWriter实现
对于TextMap格式的carrier,opentracing提供了对应的TextMapReader接口设计,并提供了TextCarrier实现,但是它是对于map[string]string格式的数据处理的。我们从grpc的md拿数据,那么md的格式是map[string][]string, 数据格式不同,所以opentracing中就也实现了metadataReaderWriter。不过它就是提供给GRPCToContext函数用的,没有开放出来。
使用经验
gin项目按照controller、service、dao进行分层,每层都要加链路追踪吗?
在链路追踪 (如使用Jaeger、Zipkin等)的上下文中,理论上你可以在应用程序的任何层(包括controller层、service层、dao层等)添加追踪信息。然而,是否需要在每一层都显式地添加追踪信息,取决于你的具体需求和应用的架构。
一般原则
1. 关键路径和瓶颈:首先关注那些对性能或业务逻辑至关重要的路径和可能的瓶颈点。在这些地方添加跟踪信息可以更有效地帮助你定位问题。
2. 透明性: 尽可能让追踪的添加过程对你的业务逻辑保持透明。这意味着你不需要在业务代码