为什么微服务必须接入链路追踪
微服务架构下,一次用户请求可能经过API网关、认证服务、业务服务、缓存、数据库等多个节点。当接口响应变慢或返回异常,仅靠单个服务的日志无法定位瓶颈所在。链路追踪通过为每个请求分配全局唯一的TraceID,在跨服务调用时通过Context传播,实现请求路径的完整可视化。
OpenTelemetry是CNCF的可观测性标准,合并了OpenTracing和OpenCensus两个项目,覆盖Traces、Metrics、Logs三大信号。Go微服务链路追踪首选OpenTelemetry SDK,避免被特定厂商的Agent绑定。
SDK初始化与Provider配置
安装核心依赖:
go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
go get go.opentelemetry.io/otel/sdk
初始化TracerProvider,连接到OTLP Collector(Jaeger、Tempo等后端均支持OTLP协议):
func initTracer(ctx context.Context, endpoint string) (*sdktrace.TracerProvider, error) {
exporter, err := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint(endpoint),
otlptracegrpc.WithInsecure(),
)
if err != nil {
return nil, err
}
provider := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("order-service"),
attribute.String("environment", "production"),
)),
sdktrace.WithSampler(sdktrace.ParentBased(
sdktrace.TraceIDRatioBased(0.1),
)),
)
otel.SetTracerProvider(provider)
return provider, nil
}
Sampler配置为10%采样率,生产环境全量采样的存储成本极高。ParentBased保证被采样的Trace其子Span全部保留,避免断链。
HTTP服务自动Instrumentation
使用otelhttp中间件为HTTP服务自动注入Span:
import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/orders", handleOrders)
handler := otelhttp.NewHandler(mux, "order-service",
otelhttp.WithMessageEvents(otelhttp.Read, otelhttp.Write),
)
http.ListenAndServe(":8080", handler)
}
otelhttp自动为每个请求创建Span,记录HTTP方法、路径、状态码。业务代码无需手动管理Span生命周期。
跨服务调用传播TraceContext
服务A调用服务B时,必须将TraceContext注入HTTP Header。使用otelhttp.Transport包装HTTP Client:
var httpClient = &http.Client{
Transport: otelhttp.NewTransport(http.DefaultTransport),
}
func callUserService(ctx context.Context, userID string) ([]byte, error) {
req, _ := http.NewRequestWithContext(ctx, "GET", "http://user-service:8081/api/user/"+userID, nil)
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
otelhttp.Transport在发送请求时自动将当前Context中的TraceID和SpanID写入W3C Trace-Parent Header,接收端otelhttp中间件自动解析。整个过程对业务代码透明。
手动创建Span记录业务逻辑
关键业务逻辑手动创建Span,补充细粒度耗时数据:
func processOrder(ctx context.Context, order Order) error {
ctx, span := otel.Tracer("order-service").Start(ctx, "processOrder")
defer span.End()
span.SetAttributes(
attribute.String("order.id", order.ID),
attribute.Float64("order.amount", order.Amount),
)
if err := validateOrder(ctx, order); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return err
}
return nil
}
RecordError将异常信息附加到Span,在Trace UI中能看到红色标记的失败Span。
gRPC服务的链路追踪
gRPC服务使用拦截器实现自动Instrumentation:
import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
server := grpc.NewServer(
grpc.StatsHandler(otelgrpc.NewServerHandler()),
)
Client端同样包装:
conn, _ := grpc.Dial(target,
grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
)
拦截器自动传播TraceContext,gRPC调用的Span自动关联到调用链中。
采样策略与成本控制
生产环境链路追踪的数据量增长极快。按10%采样率、日均1亿请求计算,每天产生1000万条Trace数据。如果每条Trace平均包含10个Span,存储成本不容忽视。
推荐策略:错误请求全量采样,正常请求按比例采样。实现方式是在Sampler中增加自定义逻辑:
type ErrorAwareSampler struct {
base sdktrace.Sampler
}
func (s ErrorAwareSampler) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingResult {
// 根据业务条件决定采样率
if isHighPriority(p) {
return sdktrace.AlwaysSample().ShouldSample(p)
}
return s.base.ShouldSample(p)
}
这样P99慢请求和错误请求的Trace数据不会丢失,常规请求通过采样控制成本。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/go-yu-yan-wei-fu-wu-lian-lu-zhui-zong-opentelemetry-quan/