system column十三Tech
← 返回技术专栏
TECH

go-zero 微服务实战:分布式链路追踪的原理与配置

微服务架构下,跨服务的慢请求如何排查?本文详解 traceId 与 spanId 的设计原理,以及 go-zero 中基于 OpenTelemetry 的分布式链路追踪配置方法。

Go微服务

在单体应用时代,排查性能问题只需查看一台服务器的日志。但在微服务架构中,一个用户请求可能流经十几个甚至几十个服务节点,如何追踪请求的完整路径、定位性能瓶颈?在十三Tech 的分布式系统中,链路追踪是我们排查跨服务慢请求的标配工具。本文将从 traceId 和 spanId 的设计原理讲起,带你掌握 go-zero 中分布式链路追踪的实战配置。

为什么需要链路追踪

仅靠 request_id 可以将多个服务器上的日志串联起来,但 request_id 只能标识"这是一次请求",却无法表达服务之间的调用关系。从日志中你无法直观地看出:A 服务调用了 B 服务,B 服务又调用了 C 服务,而 C 服务的响应慢导致了整体延迟。

因此,业界普遍采用 traceId + spanId 两个维度来记录调用关系:

  • traceId:全局唯一标识,串联单次请求的完整链路(等价于 requestId)
  • spanId:标识每一次 RPC 调用,记录调用方的开始时间、耗时、服务名等元信息

通过这种方式,我们可以在链路追踪系统中清晰地看到请求的服务拓扑、各节点的耗时分布,从而精准定位性能瓶颈。

spanId 的生成与传递机制

生成时机

当服务 A 向服务 B 发起 RPC 调用前,A 会从当前上下文中获取 traceId 和当前的 spanId,然后依据规则生成一个新的 spanId(代表本次 RPC 调用),将 traceId 和新 spanId 序列化后装配到请求体(通常是 HTTP Header 或 gRPC Metadata)中,发送给服务 B。

传递与恢复

服务 B 收到请求后,从请求体中反序列化出 traceId 和 spanId,并设置到本地线程上下文中,以便 B 在向下游发起调用时继续使用。B 处理完请求返回响应前,会计算本次调用的执行时间,将 span 信息上报到追踪系统(如 Jaeger、Zipkin)。

go-zero 中的链路追踪配置

go-zero 从 v1.4 版本开始内置了基于 OpenTelemetry 的链路追踪支持,配置非常简单。

配置文件

在服务的 etc/{your-service}.yaml 中添加 Telemetry 配置:

Telemetry:
  Name: your-service-name
  Endpoint: http://127.0.0.1:14268/api/traces
  Sampler: 1.0
  Batcher: jaeger

配置项说明:

  • Name:当前服务在链路追踪系统中的标识名
  • Endpoint:追踪系统的接收端点,Jaeger 默认端口为 14268
  • Sampler:采样率,1.0 表示全量采样,0.1 表示采样 10% 的请求
  • Batcher:追踪后端类型,支持 jaegerzipkin

配置完成后重启服务,go-zero 会以拦截器的形式自动往 ctx 中注入 trace_idspan_id

RPC 拦截器实现

go-zero 在 RPC 客户端拦截器中完成了 trace 信息的自动传递:

// UnaryTracingInterceptor 返回一个基于 OpenTelemetry 的 gRPC 客户端拦截器
func UnaryTracingInterceptor(ctx context.Context, method string, req, reply any,
	cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
	ctx, span := startSpan(ctx, method, cc.Target())
	defer span.End()

	ztrace.MessageSent.Event(ctx, 1, req)
	err := invoker(ctx, method, req, reply, cc, opts...)
	ztrace.MessageReceived.Event(ctx, 1, reply)
	if err != nil {
		s, ok := status.FromError(err)
		if ok {
			span.SetStatus(codes.Error, s.Message())
			span.SetAttributes(ztrace.StatusCodeAttr(s.Code()))
		} else {
			span.SetStatus(codes.Error, err.Error())
		}
		return err
	}

	span.SetAttributes(ztrace.StatusCodeAttr(gcodes.OK))
	return nil
}

核心流程:

  1. startSpan 从上下文中提取父 span 信息,创建新的 span
  2. 在请求发送前记录 MessageSent 事件
  3. 执行实际的 RPC 调用
  4. 在响应接收后记录 MessageReceived 事件
  5. 如果调用出错,将错误信息记录到 span 中
  6. defer span.End() 确保 span 信息被正确上报

采样策略的选择

在生产环境中,全量采样(Sampler: 1.0)会对系统性能和存储造成较大压力。建议根据场景选择合适的采样策略:

  • 开发/测试环境:全量采样,便于调试
  • 生产环境低峰期:全量或高比例采样
  • 生产环境高峰期:低比例采样(如 1% ~ 10%),或结合基于延迟的自适应采样
  • 关键路径:对核心交易链路单独配置高采样率

总结

分布式链路追踪是微服务架构可观测性的三大支柱之一(Metrics、Logging、Tracing)。go-zero 通过内置的 OpenTelemetry 支持,让开发者只需几行 YAML 配置即可获得企业级的链路追踪能力。在十三Tech 的生产实践中,链路追踪不仅帮助我们快速定位了多次跨服务性能问题,也为服务依赖分析、容量规划提供了宝贵的数据支撑。

如果你正在构建微服务系统却还没有引入链路追踪,现在就是最好的时机。

参考源码