这篇文章给大家介绍go-zero中怎么追踪请求链路的,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。
微服务架构中,调用链可能很漫长,从 http
到 rpc
,又从 rpc
到 http
。而开发者想了解每个环节的调用情况及性能,最佳方案就是 全链路跟踪。
追踪的方法就是在一个请求开始时生成一个自己的 spanID
,随着整个请求链路传下去。我们则通过这个 spanID
查看整个链路的情况和性能问题。
下面来看看 go-zero
的链路实现。
spancontext:保存链路的上下文信息「traceid,spanid,或者是其他想要传递的内容」
span:链路中的一个操作,存储时间和某些信息
propagator: trace
传播下游的操作「抽取,注入」
noop:实现了空的 tracer
实现
在介绍 span
之前,先引入 context
。SpanContext 保存了分布式追踪的上下文信息,包括 Trace id,Span id 以及其它需要传递到下游的内容。OpenTracing 的实现需要将 SpanContext 通过某种协议 进行传递,以将不同进程中的 Span 关联到同一个 Trace 上。对于 HTTP 请求来说,SpanContext 一般是采用 HTTP header 进行传递的。
下面是 go-zero
默认实现的 spanContext
type spanContext struct { traceId string // TraceID 表示tracer的全局唯一ID spanId string // SpanId 标示单个trace中某一个span的唯一ID,在trace中唯一 }
同时开发者也可以实现 SpanContext
提供的接口方法,实现自己的上下文信息传递:
type SpanContext interface { TraceId() string // get TraceId SpanId() string // get SpanId Visit(fn func(key, val string) bool) // 自定义操作TraceId,SpanId }
一个 REST 调用或者数据库操作等,都可以作为一个 span
。 span
是分布式追踪的最小跟踪单位,一个 Trace 由多段 Span 组成。追踪信息包含如下信息:
type Span struct { ctx spanContext // 传递的上下文 serviceName string // 服务名 operationName string // 操作 startTime time.Time // 开始时间戳 flag string // 标记开启trace是 server 还是 client children int // 本 span fork出来的 childsnums }
从 span
的定义结构来看:在微服务中, 这就是一个完整的子调用过程,有调用开始 startTime
,有标记自己唯一属性的上下文结构 spanContext
以及 fork 的子节点数。
在 go-zero
中http,rpc中已经作为内置中间件集成。我们以 http,rpc 中,看看 tracing
是怎么使用的:
func TracingHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // **1** carrier, err := trace.Extract(trace.HttpFormat, r.Header) // ErrInvalidCarrier means no trace id was set in http header if err != nil && err != trace.ErrInvalidCarrier { logx.Error(err) } // **2** ctx, span := trace.StartServerSpan(r.Context(), carrier, sysx.Hostname(), r.RequestURI) defer span.Finish() // **5** r = r.WithContext(ctx) next.ServeHTTP(w, r) }) } func StartServerSpan(ctx context.Context, carrier Carrier, serviceName, operationName string) ( context.Context, tracespec.Trace) { span := newServerSpan(carrier, serviceName, operationName) // **4** return context.WithValue(ctx, tracespec.TracingKey, span), span } func newServerSpan(carrier Carrier, serviceName, operationName string) tracespec.Trace { // **3** traceId := stringx.TakeWithPriority(func() string { if carrier != nil { return carrier.Get(traceIdKey) } return "" }, func() string { return stringx.RandId() }) spanId := stringx.TakeWithPriority(func() string { if carrier != nil { return carrier.Get(spanIdKey) } return "" }, func() string { return initSpanId }) return &Span{ ctx: spanContext{ traceId: traceId, spanId: spanId, }, serviceName: serviceName, operationName: operationName, startTime: timex.Time(), // 标记为server flag: serverFlag, } }
将 header -> carrier,获取 header 中的traceId等信息
开启一个新的 span,并把**「traceId,spanId」**封装在context中
从上述的 carrier「也就是header」获取traceId,spanId。
看header中是否设置
如果没有设置,则随机生成返回
从 request
中产生新的ctx,并将相应的信息封装在 ctx 中,返回
从上述的 context,拷贝一份到当前的 request
这样就实现了 span
的信息随着 request
传递到下游服务。
在 rpc 中存在 client, server
,所以从 tracing
上也有 clientTracing, serverTracing
。 serveTracing
的逻辑基本与 http 的一致,来看看 clientTracing
是怎么使用的?
func TracingInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { // open clientSpan ctx, span := trace.StartClientSpan(ctx, cc.Target(), method) defer span.Finish() var pairs []string span.Visit(func(key, val string) bool { pairs = append(pairs, key, val) return true }) // **3** 将 pair 中的data以map的形式加入 ctx ctx = metadata.AppendToOutgoingContext(ctx, pairs...) return invoker(ctx, method, req, reply, cc, opts...) } func StartClientSpan(ctx context.Context, serviceName, operationName string) (context.Context, tracespec.Trace) { // **1** if span, ok := ctx.Value(tracespec.TracingKey).(*Span); ok { // **2** return span.Fork(ctx, serviceName, operationName) } return ctx, emptyNoopSpan }
获取上游带下来的 span 上下文信息
从获取的 span 中创建新的 ctx,span「继承父span的traceId」
将生成 span 的data加入ctx,传递到下一个中间件,流至下游
go-zero
通过拦截请求获取链路traceID,然后在中间件函数入口会分配一个根Span,然后在后续操作中会分裂出子Span,每个span都有自己的具体的标识,Finsh之后就会汇集在链路追踪系统中。
开发者可以通过 ELK
工具追踪 traceID
,看到整个调用链。同时 go-zero
并没有提供整套 trace
链路方案,开发者可以封装 go-zero
已有的 span
结构,做自己的上报系统,接入 jaeger, zipkin
等链路追踪工具。
关于go-zero中怎么追踪请求链路的就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。