找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1499

积分

0

好友

190

主题
发表于 3 天前 | 查看: 9| 回复: 0

1. 前言

在 OpenTelemetry 的语境中,采样(Sampling)指的是决定是否收集并上报包含 Span 的追踪信息。

对于订单类等核心且QPS不高的服务,进行全量采样是可行的。然而,对于微博、抖音这类读多写少的内容型服务,全量采样的成本极高,既不现实也无必要。此时,除了按百分比采样外,一种更保守的策略是:仅对异常的请求(例如通过客户端埋点、指标告警或客服反馈识别)进行流量重放,并在此过程中采集完整的追踪信息。

2. 根据特定Header决定是否采样

为了实现上述策略,我们需要一个明确的标识来指示某个请求必须被采样。以HTTP请求为例,可以约定一个特殊的请求头。

X-Force-Trace: 1

2.1 上游服务实现

OpenTelemetry SDK 预留了 Sampler 接口,允许开发者自定义采样逻辑。

// Sampler decides whether a trace should be sampled and exported.
type Sampler interface {
    // ShouldSample returns a SamplingResult based on a decision made from the passed parameters.
    ShouldSample(parameters SamplingParameters) SamplingResult
    // Description returns information describing the Sampler.
    Description() string
}

以下是一个在 Gin 框架中实现的完整示例。完整代码可参考项目:vearne/otel-test

主程序 (main.go):

func main() {
    tp := InitTracerProvider()
    defer func() { _ = tp.Shutdown(context.Background()) }()

    r := gin.New()
    r.Use(headerToCtxMiddleware("X-Force-Trace")) // ① 将Header值注入Context
    r.Use(otelgin.Middleware("gin-header-sampler")) // ② 使用官方OpenTelemetry Gin中间件

    r.GET("/ping", func(c *gin.Context) {
        _, span := otel.Tracer("").Start(c.Request.Context(), "ping")
        defer span.End()
        c.String(200, "pong")
    })
    _ = r.Run(":8080")
}

追踪提供者初始化 (tracer.go):

func InitTracerProvider() *sdktrace.TracerProvider {
    ctx := context.Background()
    exporter, err := otlptracehttp.New(ctx)
    if err != nil {
        log.Fatalf("new otlp trace http exporter failed: %v", err)
    }

    tp := sdktrace.NewTracerProvider(
        // 关键:使用自定义的基于Header的采样器
        sdktrace.WithSampler(NewHeaderSampler("X-Force-Trace", "1")),
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(initResource()),
    )
    otel.SetTracerProvider(tp)
    otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
    return tp
}

Gin中间件 (middleware.go): 用于将Header值传递到Context中。

func headerToCtxMiddleware(headerKey string) gin.HandlerFunc {
    return func(c *gin.Context) {
        v := c.GetHeader(headerKey)
        // 将值写入Context,后续采样器可以读取
        ctx := context.WithValue(c.Request.Context(), "http.header."+headerKey, v)
        c.Request = c.Request.WithContext(ctx)
        c.Next()
    }
}

自定义采样器 (sampler.go):

type headerSampler struct {
    key, value string
}

// NewHeaderSampler 返回一个自定义的 trace.Sampler
func NewHeaderSampler(headerKey, headerValue string) trace.Sampler {
    return &headerSampler{key: headerKey, value: headerValue}
}

func (s *headerSampler) ShouldSample(p trace.SamplingParameters) trace.SamplingResult {
    // 从父级Context中读取Gin中间件预先设置的值
    v := p.ParentContext.Value("http.header." + s.key)
    if str, ok := v.(string); ok && strings.TrimSpace(str) == s.value {
        return trace.SamplingResult{Decision: trace.RecordAndSample} // 记录并采样
    }
    return trace.SamplingResult{Decision: trace.Drop} // 丢弃
}

func (s *headerSampler) Description() string {
    return "HeaderSampler"
}

值得注意的是,无论是否采样,Span信息都会随着 context.Context 向下传递,这是 云原生 可观测性中上下文传播的基础。

2.2 下游服务

在微服务调用链中,若服务A调用服务B,则A为上游服务,B为下游服务。上游服务通过W3C TraceContext规范将采样决策传递给下游服务。

traceparent Header的格式如下,其最后一个字节(flags)的最低位即为采样标志位:

  • 01 → 已采样
  • 00 → 未采样
traceparent: 00-6ef809a63797e155758fc91d5cec9cae-f85da44ea72a809d-01

Q: 如果上游服务没有传递TraceContext,下游服务如何行为?

A: 下游服务需要在配置中显式定义此场景下的行为。例如,可以配置为:当存在父级追踪上下文时遵从上游决策,否则永不采样。

func InitTracerProvider() *sdktrace.TracerProvider {
    ...
    tp := sdktrace.NewTracerProvider(
        // 使用ParentBased采样器:有父上下文则遵从,否则使用NeverSample
        sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.NeverSample())),
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(initResource()),
    )
    ...
    return tp
}

3. 总结

OpenTelemetry的设计非常完备,赋予了使用者高度的灵活性来实现个性化采样策略。开发者可以轻松地基于用户ID、入口IP、特定应用、调用链入口或关键业务标识等维度来动态开启采样,从而在观测成本与问题排查效率间取得最佳平衡。




上一篇:AI时代数据库选型:从LAMP到云原生与三要素分析法
下一篇:Proxmox VE 9.0虚拟化平台深度解析:基于KVM/LXC的私有云与超融合部署实战
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 21:11 , Processed in 0.245648 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表