OpenTelemetry个性化采样-根据特定Header决定是否采样
版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | https://vearne.cc
建议阅读,萌叔2024年的文章 OpenTelemetry原理及实战
1.前言
统一术语:
采样: sample,在OpenTelemetry的语境下,是指收集tracing(包含span)信息,并上报
对于订单类的服务而言,由于服务特别重要,QPS不高,全量采样问题不大。
但是对于内容型的服务,比如微博、抖音服务,这类读多写少的服务,全量采样成本巨大,
不仅不现实,也没有必要。此时当然可以考虑按照百分比采样,
另一种更为保守的做法,只收集异常的请求(客户端收集,指标告警,客服报障),进行流量重放,
并同时采集tracing信息。
2.根据特定Header决定是否采样
这时候我们需要特定的标识表明这个请求需要强制采样。
以HTTP请求为例,可以给定特殊的Header
X-Force-Trace: 1
2.1 上游服务
OpenTelemetry的SDK预留了Sampler接口,用于定义个性化的采样需求(是否采样)
// Sampler decides whether a trace should be sampled and exported.
type Sampler interface {
// DO NOT CHANGE: any modification will not be backwards compatible and
// must never be done outside of a new major release.
// ShouldSample returns a SamplingResult based on a decision made from the
// passed parameters.
ShouldSample(parameters SamplingParameters) SamplingResult
// DO NOT CHANGE: any modification will not be backwards compatible and
// must never be done outside of a new major release.
// Description returns information describing the Sampler.
Description() string
// DO NOT CHANGE: any modification will not be backwards compatible and
// must never be done outside of a new major release.
}
完整代码见
vearne/otel-test
比如对使用gin的http服务时
func main() {
tp := InitTracerProvider()
defer func() { _ = tp.Shutdown(context.Background()) }()
r := gin.New()
r.Use(headerToCtxMiddleware("X-Force-Trace")) // ① 把 header 塞进 ctx
r.Use(otelgin.Middleware("gin-header-sampler")) // ② 官方 otel-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")
}
func InitTracerProvider() *sdktrace.TracerProvider {
ctx := context.Background()
exporter, err := otlptracehttp.New(ctx)
if err != nil {
log.Fatalf("new otlp trace grpc exporter failed: %v", err)
}
tp := sdktrace.NewTracerProvider(
// 使用自定以的采样器
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
}
func headerToCtxMiddleware(headerKey string) gin.HandlerFunc {
return func(c *gin.Context) {
v := c.GetHeader(headerKey)
// 写进 context,sampler 里能取到
ctx := context.WithValue(c.Request.Context(), "http.header."+headerKey, v)
c.Request = c.Request.WithContext(ctx)
c.Next()
}
}
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 {
// 从 parent context 里取 attributes(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进行传递
type traceContextKeyType int
const currentSpanKey traceContextKeyType = iota
// ContextWithSpan returns a copy of parent with span set as the current Span.
func ContextWithSpan(parent context.Context, span Span) context.Context {
return context.WithValue(parent, currentSpanKey, span)
}
2.2 下游服务
统一术语:
在A服务中,涉及调用B服务的场景
在这个场景中,A是上游服务,B是下游服务。
这里主要是强调tracing信息从上游服务流转到下游服务。
上游服务通过TraceContext将是否要采样的决策传递给下游服务
W3C TraceContext 规范
traceparent: 00-6ef809a63797e155758fc91d5cec9cae-f85da44ea72a809d-01
traceparent: 00-<32-byte-trace-id>-<16-byte-span-id>-<2-byte-flags>
最后一个字节(flags)的最低位 bit 就是 sampled 位:
- 01 → 已采样
- 00 → 未采样
Q: 如果上游服务没有传递TraceContext呢?
下游服务可以按照如下方式配置
func InitTracerProvider() *sdktrace.TracerProvider {
ctx := context.Background()
exporter, err := otlptracehttp.New(ctx)
if err != nil {
log.Fatalf("new otlp trace grpc exporter failed: %v", err)
}
tp := sdktrace.NewTracerProvider(
/*
如果传递了TraceContext,按照上游的决策决定是否采样
否则按照Never Sample
*/
sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.NeverSample())),
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(initResource()),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
return tp
}
A:
上游服务没有传递TraceContext的情况下的行为,需要在配置中显示指出。
3.总结
OpenTelmetry设计是很完备的,使用者可以非常方便的进行个性化采样。
按用户ID、入口IP、应用、调用链入口、业务标识等配置开启采样。
