热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

go链路追踪_GoZero是如何追踪你的请求链路?

“go-zero是一个集成了各种工程实践的web和rpc框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。”序言微服务架构中,调用链可

710fe209d8a3863d73a119106e9531e0.png

“ go-zero 是一个集成了各种工程实践的 web 和 rpc 框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。”

序言

微服务架构中,调用链可能很漫长,从 http 到 rpc ,又从 rpc 到 http 。而开发者想了解每个环节的调用情况及性能,最佳方案就是 全链路跟踪。

追踪的方法就是在一个请求开始时生成一个自己的 spanID ,随着整个请求链路传下去。我们则通过这个 spanID 查看整个链路的情况和性能问题。

下面来看看 go-zero 的链路实现。

代码结构

  • spancontext:保存链路的上下文信息「traceid,spanid,或者是其他想要传递的内容」

  • span:链路中的一个操作,存储时间和某些信息

  • propagator: trace 传播下游的操作「抽取,注入」

  • noop:实现了空的 tracer 实现

76b279cf64d87f179819190f3c6d7c4a.png

概念

SpanContext

在介绍 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的全局唯一IDspanId 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
}

Span

一个 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 是怎么使用的:

HTTP

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,
}
}

  1. 将 header -> carrier,获取 header 中的 traceId 等信息

  2. 开启一个新的 span,并把「traceId,spanId」封装在 context 中

  3. 从上述的 carrier「也就是 header」获取 traceId,spanId

  • 看 header 中是否设置

  • 如果没有设置,则随机生成返回

从 request 中产生新的 ctx,并将相应的信息封装在 ctx 中,返回

从上述的 context,拷贝一份到当前的 request

b919567109c42c5bd968ef0f2361f475.png

这样就实现了 span 的信息随着 request 传递到下游服务。

RPC

在 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
}

  1. 获取上游带下来的 span 上下文信息

  2. 从获取的 span 中创建新的 ctx,span「继承父 span 的 traceId」

  3. 将生成 span 的 data 加入 ctx,传递到下一个中间件,流至下游

总结

go-zero 通过拦截请求获取链路 traceID,然后在中间件函数入口会分配一个根 Span,然后在后续操作中会分裂出子 Span,每个 span 都有自己的具体的标识,Finsh 之后就会汇集在链路追踪系统中。开发者可以通过 ELK 工具追踪 traceID ,看到整个调用链。

同时 go-zero 并没有提供整套 trace 链路方案,开发者可以封装 go-zero 已有的 span 结构,做自己的上报系统,接入 jaeger, zipkin 等链路追踪工具。

参考

  • go-zero trace

  • 开放分布式追踪(OpenTracing)入门与 Jaeger 实现

同时欢迎大家使用 go-zero 并加入我们,https://github.com/tal-tech/go-zero

480ae6412b23790667cc361e9f1f1350.png




推荐阅读
  • 本文探讨了如何通过一系列技术手段提升Spring Boot项目的并发处理能力,解决生产环境中因慢请求导致的系统性能下降问题。 ... [详细]
  • 本文探讨了Web开发与游戏开发之间的主要区别,旨在帮助开发者更好地理解两种开发领域的特性和需求。文章基于作者的实际经验和网络资料整理而成。 ... [详细]
  • 尽管PHP是一种强大且灵活的Web开发语言,但开发者在使用过程中常会陷入一些典型的陷阱。本文旨在列出PHP开发中最为常见的10种错误,并提供相应的预防建议。 ... [详细]
  • 在 Android 开发中,通过 Intent 启动 Activity 或 Service 时,可以使用 putExtra 方法传递数据。接收方可以通过 getIntent().getExtras() 获取这些数据。本文将介绍如何使用 RoboGuice 框架简化这一过程,特别是 @InjectExtra 注解的使用。 ... [详细]
  • 优化Flask应用的并发处理:解决Mysql连接过多问题
    本文探讨了在Flask应用中通过优化后端架构来应对高并发请求,特别是针对Mysql 'too many connections' 错误的解决方案。我们将介绍如何利用Redis缓存、Gunicorn多进程和Celery异步任务队列来提升系统的性能和稳定性。 ... [详细]
  • QNX 微内核(procnto-instr)的监测版本内置了高级跟踪与分析工具,能够实现实时系统监控。该模块适用于单处理器及多处理器系统。 ... [详细]
  • Consul 单节点与集群环境构建指南
    本文详细介绍了如何安装和配置 Consul 以支持服务注册与发现、健康检查等功能,包括单节点和集群环境的搭建步骤。 ... [详细]
  • .NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了
    .NETCore中的一个接口多种实现的依赖注入与动态选择看这篇就够了最近有个需求就是一个抽象仓储层接口方法需要SqlServer以及Oracle两种实现方式,为了灵活我在依赖注入的 ... [详细]
  • 解决MySQL错误2002:无法建立数据库连接
    本文详细描述了在Digital Ocean服务器上托管的多个WordPress站点突然出现数据库连接错误的情况,并提供了有效的解决方案。 ... [详细]
  • Spring框架中@Autowired注解对接口与其实现类的绑定机制解析
    本文深入探讨了Spring框架中@Autowired注解的工作原理,特别是当其应用于接口而非实现类时的情况,以及如何处理接口拥有多个实现类的情形。旨在为开发者提供有效的指导。 ... [详细]
  • 本文详细记录了一次 HBase RegionServer 异常宕机的情况,包括具体的错误信息和可能的原因分析。通过此案例,探讨了如何有效诊断并解决 HBase 中常见的 RegionServer 挂起问题。 ... [详细]
  • 本文详细探讨了 org.apache.hadoop.ha.HAServiceTarget 类中的 checkFencingConfigured 方法,包括其功能、应用场景及代码示例。通过实际代码片段,帮助开发者更好地理解和使用该方法。 ... [详细]
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • 并发编程 12—— 任务取消与关闭 之 shutdownNow 的局限性
    Java并发编程实践目录并发编程01——ThreadLocal并发编程02——ConcurrentHashMap并发编程03——阻塞队列和生产者-消费者模式并发编程04——闭锁Co ... [详细]
  • Python作为一种广泛使用的高级编程语言,以其简洁的语法、强大的功能和丰富的库支持著称。本文将详细介绍Python的主要特点及其在现代软件开发中的应用。 ... [详细]
author-avatar
四只猪1984
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有