第一章:TikTok、字节、Uber强制接入OpenTelemetry的底层动因
观测即基础设施:从被动排障到主动治理的范式迁移
在微服务规模突破万级、跨云多运行时(K8s + Serverless + Edge)成为常态的今天,传统APM工具面临三大不可解矛盾:探针侵入性导致性能抖动(如Java Agent平均增加8–12% CPU开销)、厂商锁定使跨云链路无法对齐(如AWS X-Ray与GCP Trace ID格式不兼容)、指标/日志/追踪三者元数据割裂导致根因定位耗时超47分钟(Uber 2023 SRE报告)。OpenTelemetry通过标准化信号采集协议(OTLP over gRPC/HTTP)、无侵入SDK(支持自动注入+手动埋点双模式)和统一语义约定(Semantic Conventions v1.21+),将可观测性从“附加能力”升格为与网络、存储并列的基础设施层。
成本与合规的双重倒逼机制
字节跳动内部审计显示,自建监控体系年运维成本达$23M,其中38%用于适配新框架(如Rust服务接入需重写采集模块)。而OTel SDK原生支持Go/Rust/Python/Java等12+语言,配合Collector可实现“一次配置、全域生效”。更关键的是GDPR与《个人信息出境安全评估办法》要求全链路数据最小化采集——OTel的Resource属性与Span属性分离设计,允许在Collector层通过processor.attributes动态脱敏用户ID字段:
# otel-collector-config.yaml 片段
processors:
attributes/user_anonymize:
actions:
- key: "user.id"
action: delete # 强制移除敏感字段
- key: "service.name"
action: insert
value: "tiktok-video-frontend" # 统一服务标识
生态协同带来的网络效应
当TikTok将OTel作为所有新服务默认观测标准后,其供应商(如CDN、支付网关)被迫提供OTLP endpoint;Uber则将OTel Collector深度集成至其自研调度系统Peloton,使trace采样率从5%提升至100%且内存占用下降62%。这种“头部驱动→生态对齐→标准固化”的路径,本质上是用可观测性统一语言重构分布式系统的信任基座。
第二章:etcd——云原生基石项目的可观测性演进
2.1 OpenTelemetry SDK在etcd v3.5+中的嵌入式集成机制
etcd v3.5+ 将 OpenTelemetry SDK 直接编译进核心二进制,通过 go.opentelemetry.io/otel/sdk 的轻量级 tracer provider 实现零依赖注入。
初始化时机与配置入口
启动时通过 embed.WithTracerProvider() 注册全局 trace provider,自动拦截 gRPC server、raft transport 和 WAL 写入路径:
// etcdserver/embed/config.go 片段
func NewConfig() *Config {
return &Config{
TracerProvider: sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.01))),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("etcd-server"),
)),
),
}
}
此配置启用基于 TraceID 的 1% 采样,避免高负载下 telemetry 过载;
ServiceNameKey确保服务拓扑可识别,ParentBased兼容外部调用链透传。
关键追踪点映射表
| 组件 | Span 名称 | 触发条件 |
|---|---|---|
| gRPC Server | etcdserver.grpc.recv |
每个 RPC 请求进入时创建 |
| Raft Apply | raft.apply |
日志条目被应用到状态机时 |
| Backend Write | backend.write |
BoltDB 事务提交前注入 span |
数据同步机制
Span 数据经 sdktrace.NewBatchSpanProcessor 异步导出至 OTLP HTTP endpoint(默认 /v1/traces),与 etcd 的 --experimental-enable-pprof 同步启用可观测性增强。
2.2 分布式键值操作的Span语义建模与Trace上下文透传实践
在分布式键值系统(如Redis Cluster、TiKV)中,一次GET/SET可能跨越代理层、分片路由、存储节点与副本同步链路。需将业务操作精准映射为可追溯的Span单元。
Span语义建模原则
kv.get:span.kind=client,peer.service=redis-shard-03kv.set:附加db.statement="SET user:1001 {json}"与db.ttl=3600- 失败Span必须携带
error.type=timeout与rpc.status_code=DEADLINE_EXCEEDED
Trace上下文透传实现
// 使用OpenTelemetry SDK注入/提取TraceContext
public String getWithTrace(String key) {
Context parent = Context.current(); // 当前Span上下文
Span span = tracer.spanBuilder("kv.get")
.setParent(parent) // 继承调用链
.setAttribute("db.name", "redis")
.setAttribute("db.operation", "GET")
.startSpan();
try (Scope scope = span.makeCurrent()) {
return redisClient.get(key); // 自动携带traceparent header
} finally {
span.end();
}
}
逻辑分析:
makeCurrent()确保下游HTTP/gRPC调用自动注入traceparent;setAttribute显式标注KV操作语义,便于APM平台聚合分析。参数key不作为Span属性,避免敏感信息泄露与高基数问题。
上下文透传关键字段对照表
| 字段名 | 用途 | 示例值 |
|---|---|---|
traceparent |
W3C标准追踪标识 | 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01 |
tracestate |
跨厂商状态传递 | congo=t61rcWkgMzE |
ot-baggage |
业务自定义透传键值对 | user_id=1001,region=shanghai |
graph TD
A[Client App] -->|traceparent + baggage| B[API Gateway]
B --> C[Shard Router]
C --> D[Redis Node A]
C --> E[Redis Node B]
D -->|replica sync| F[TiKV Raft Group]
2.3 etcd Raft日志同步链路的指标埋点设计与Prometheus对接
数据同步机制
etcd v3.5+ 在 raftNode 和 applyWait 阶段注入关键观测点,覆盖日志复制(raft_propose → raft_send_append → raft_recv_append_response)全路径。
核心埋点指标
etcd_disk_wal_fsync_duration_seconds:WAL落盘延迟etcd_network_peer_round_trip_time_seconds:Peer间RTTetcd_raft_apply_wait_duration_seconds:日志应用前等待耗时
Prometheus采集配置示例
# prometheus.yml 片段
- job_name: 'etcd'
scheme: https
tls_config:
ca_file: /etc/ssl/etcd/ca.crt
static_configs:
- targets: ['10.0.1.10:2379', '10.0.1.11:2379']
该配置启用mTLS认证,确保指标端点
/metrics安全暴露。etcdctl --endpoints=https://10.0.1.10:2379 endpoint status可验证指标服务可达性。
2.4 基于OTLP exporter的多租户元数据注入与采样策略配置
在多租户可观测性平台中,OTLP exporter 需在发送前动态注入租户标识与上下文元数据,并协同服务端采样策略实现资源隔离。
元数据注入机制
通过 resource_attributes 扩展点注入 tenant.id、environment 等关键标签:
exporters:
otlp/tenant-aware:
endpoint: "collector.example.com:4317"
headers:
x-tenant-id: "${TENANT_ID}" # 透传至网关鉴权
resource_attributes:
- key: "tenant.id"
value: "${TENANT_ID}"
action: insert
该配置确保所有 trace/metric/log 自动携带租户维度,为后端路由与配额控制提供依据。
采样策略协同
支持 per-tenant 动态采样率配置(单位:每秒样本数):
| 租户ID | 采样率(TPS) | 优先级 | 启用熔断 |
|---|---|---|---|
| prod-a | 500 | high | ✅ |
| dev-b | 10 | low | ❌ |
数据流示意
graph TD
A[Instrumentation] --> B[SDK Processor]
B --> C{Tenant Context?}
C -->|Yes| D[Inject resource attrs + header]
C -->|No| E[Drop or default tenant]
D --> F[OTLP Exporter]
F --> G[Collector Gateway]
2.5 生产环境Trace采样率调优与内存泄漏规避实战
动态采样策略设计
避免全量埋点导致的GC压力,采用分层采样:核心接口100%,普通接口1%~5%,异常链路自动升采样至100%。
// 基于QPS与错误率的自适应采样器
public boolean shouldSample(SpanContext ctx) {
double baseRate = config.getBaseSamplingRate(); // 默认0.01
if (ctx.hasError()) return true; // 错误链路强制采样
if (ctx.getService().equals("payment")) return true; // 关键服务保底
return Math.random() < baseRate * Math.min(1.0, qps / 100.0); // QPS越高,采样率线性提升
}
逻辑分析:qps / 100.0 实现流量感知——当QPS达200时,采样率翻倍(0.02),兼顾可观测性与性能;hasError()保障故障可追溯。
内存泄漏关键防控点
- 禁止将
Span或Tracer存入静态集合 - 使用
WeakReference<Span>缓存非关键上下文 - 每个
Span绑定ThreadLocal生命周期,显式调用span.end()
| 风险操作 | 安全替代方案 | GC影响 |
|---|---|---|
static Map<String, Span> cache |
ConcurrentHashMap<String, WeakReference<Span>> |
⬇️ 降低OOM风险 |
new Thread(() -> trace()).start() |
executor.submit(() -> trace()) + Scope.close() |
⬇️ 防止ThreadLocal泄漏 |
graph TD
A[请求进入] --> B{QPS > 100?}
B -->|是| C[采样率 × 2]
B -->|否| D[维持基础采样率]
C & D --> E[创建Span]
E --> F[执行业务逻辑]
F --> G[Span.end()]
G --> H[异步上报+本地清理]
第三章:CockroachDB——分布式SQL数据库的可观测性落地
3.1 SQL执行生命周期的Span切分原则与Context传播验证
SQL执行生命周期需在关键节点精准切分Span,确保链路追踪语义完整。核心切分点包括:连接获取、SQL预编译、参数绑定、执行调用、结果集遍历及连接释放。
Span切分黄金准则
- 不可跨事务边界合并:同一事务内多个
executeUpdate()应归属同一Span - I/O阻塞即切分点:
ResultSet.next()调用触发新Span(含网络往返) - 上下文必须透传:
Connection/Statement需携带TracingContext
Context传播验证示例
// 使用OpenTelemetry SDK注入/提取TraceContext
Scope scope = tracer.spanBuilder("jdbc:executeQuery")
.setParent(Context.current().with(Span.wrap(parentSpanId))) // 显式继承
.startScopedSpan();
try (ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) { /* 处理行 */ } // 此处隐式创建子Span
} finally {
scope.close(); // 确保Span结束且context cleanup
}
逻辑分析:
spanBuilder().setParent()强制继承上游traceID与spanID;startScopedSpan()将当前Span绑定至线程本地Context;scope.close()触发Span上报并清理ThreadLocal,避免内存泄漏。parentSpanId需从HTTP Header或MQ消息头中提取,验证传播完整性。
| 切分阶段 | 是否创建Span | Context是否继承 | 验证方式 |
|---|---|---|---|
| Connection.acquire | 是 | 否(Root) | traceID全零校验 |
| PreparedStatement.execute | 是 | 是 | traceID/spanID比对 |
| ResultSet.next | 是(可选) | 是 | 通过@WithSpan注解覆盖 |
graph TD
A[DataSource.getConnection] -->|Span: db.connect| B[PreparedStatement.prepare]
B -->|Span: db.prepare| C[stmt.executeQuery]
C -->|Span: db.query| D[ResultSet.next]
D -->|Span: db.fetch| E[rs.getString]
3.2 分布式事务追踪中Span父子关系与SpanKind的精准标注
Span的父子关系并非仅由调用时序决定,而需结合语义上下文显式声明。错误的嵌套会导致链路断裂或指标失真。
SpanKind 的语义分类
SERVER:接收并处理请求的入口(如 HTTP Controller)CLIENT:发起远程调用的出口(如 Feign/RPC 调用器)CONSUMER:消息队列消费者(如 KafkaListener)PRODUCER:消息队列生产者(如 KafkaTemplate.send)
父子关系建立示例(OpenTelemetry Java SDK)
// 创建父 Span(SERVER)
Span serverSpan = tracer.spanBuilder("order-api")
.setSpanKind(SpanKind.SERVER)
.startSpan();
// 显式指定父上下文,创建子 Span(CLIENT)
Context parentCtx = Context.current().with(serverSpan);
Span dbSpan = tracer.spanBuilder("jdbc-query")
.setParent(parentCtx) // 关键:强制继承,不依赖线程本地
.setSpanKind(SpanKind.CLIENT)
.startSpan();
逻辑分析:
setParent(parentCtx)显式绑定父子关系,避免因异步线程切换丢失上下文;SpanKind.CLIENT标明该 Span 主动发起外部依赖调用,影响采样策略与服务拓扑渲染。
常见 SpanKind 组合语义表
| 父 SpanKind | 子 SpanKind | 典型场景 |
|---|---|---|
| SERVER | CLIENT | Web 接口内调用下游 HTTP 服务 |
| CONSUMER | INTERNAL | 消息消费后执行本地业务逻辑 |
| CLIENT | PRODUCER | RPC 客户端内部向注册中心发心跳 |
graph TD
A[HTTP SERVER] -->|SpanKind: SERVER| B[DB CLIENT]
A -->|SpanKind: CLIENT| C[Cache CLIENT]
B -->|SpanKind: INTERNAL| D[Validation]
3.3 自定义Attribute扩展机制在多版本并发控制(MVCC)监控中的应用
为精准捕获 MVCC 关键路径的事务快照行为,我们设计 SnapshotMonitorAttribute,用于标记需注入版本可见性检查逻辑的方法。
监控属性定义
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class SnapshotMonitorAttribute : Attribute
{
public string Operation { get; } // 如 "READ_COMMITTED", "REPEATABLE_READ"
public bool TrackVersionChain { get; set; } = true;
public SnapshotMonitorAttribute(string operation) => Operation = operation;
}
该属性声明式地标记事务边界方法,Operation 指定隔离级别语义,TrackVersionChain 控制是否采集历史版本链路。
运行时拦截逻辑(简略示意)
// 使用动态代理或源生成器在调用前注入监控钩子
if (method.HasAttribute<SnapshotMonitorAttribute>())
{
var attr = method.GetCustomAttribute<SnapshotMonitorAttribute>();
LogSnapshotAccess(attr.Operation, GetCurrentTxnId(), GetVisibleVersionIds());
}
通过反射获取属性元数据,并结合当前事务ID与可见版本集合,实现无侵入式可观测性增强。
| 监控维度 | 数据来源 | 用途 |
|---|---|---|
| 可见版本数 | GetVisibleVersionIds() |
识别长事务导致的版本膨胀 |
| 快照生成耗时 | Stopwatch |
定位 MVCC 初始化性能瓶颈 |
| 版本链长度 | VersionChain.Count |
判断垃圾版本清理压力 |
graph TD
A[方法调用] --> B{Has SnapshotMonitorAttribute?}
B -->|Yes| C[获取事务快照信息]
B -->|No| D[直通执行]
C --> E[记录版本可见性上下文]
E --> F[上报至监控中心]
第四章:Kratos——Bilibili开源微服务框架的OTel标准化实践
4.1 Kratos Middleware层对OTel Tracer与Meter的统一注入模式
Kratos 的 Middleware 层通过 WithMiddleware 机制,将 OpenTelemetry 的 TracerProvider 与 MeterProvider 抽象为共享上下文依赖,实现一次注册、多点复用。
统一依赖注入入口
func NewServer( // Kratos Server 构建器
opts ...server.Option,
) *http.Server {
return server.New(
server.WithMiddleware(
otelgrpc.UnaryServerInterceptor(), // 自动注入 tracer/meter
metrics.Middleware(), // 复用同一 MeterProvider
),
)
}
该构造器隐式从全局 otel.GetTracerProvider() 和 otel.GetMeterProvider() 获取实例,避免中间件重复初始化。
核心优势对比
| 特性 | 传统方式 | Kratos 统一注入 |
|---|---|---|
| 初始化次数 | 每中间件独立调用 NewTracer() |
全局单例复用 |
| 配置一致性 | 易出现采样率/Exporter 不一致 | 由 otel.SetTracerProvider() 全局管控 |
注入时序流程
graph TD
A[启动时调用 otel.SetTracerProvider] --> B[Middleware 初始化]
B --> C{自动获取 Tracer/Meter 实例}
C --> D[注入 gRPC HTTP 中间件]
4.2 gRPC拦截器中Span创建、错误分类与状态码映射规范
Span生命周期绑定
在gRPC服务器拦截器中,Span应严格绑定RPC生命周期:Start()于handler前,Finish()于handler返回后(含panic恢复路径)。避免跨goroutine泄漏。
错误语义分类
CLIENT_ERROR:InvalidArgument、NotFound、FailedPreconditionSERVER_ERROR:Internal、Unavailable、DeadlineExceededNETWORK_ERROR:底层连接中断(需从status.FromError(err)提取Code)
状态码映射表
| gRPC Code | OpenTelemetry Status Code | 语义说明 |
|---|---|---|
| OK | STATUS_CODE_OK | 成功调用 |
| InvalidArgument | STATUS_CODE_ERROR | 客户端输入校验失败 |
| Internal | STATUS_CODE_ERROR | 服务端未预期异常 |
func serverInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
span := trace.SpanFromContext(ctx)
// 设置span属性:方法名、请求大小等
span.SetAttributes(attribute.String("rpc.method", info.FullMethod))
defer func() {
if err != nil {
st := status.Convert(err)
span.SetStatus(codes.Error, st.Message()) // 显式设状态
span.SetAttributes(attribute.Int("error.code", int(st.Code())))
}
}()
return handler(ctx, req)
}
该拦截器确保Span在RPC入口创建、出口统一终结;status.Convert()安全解包gRPC错误,codes.Error准确反映业务/系统级故障,避免将NotFound误标为ERROR。
4.3 HTTP中间件与OpenTelemetry Propagator的双向兼容性适配
HTTP中间件需无缝桥接不同传播格式(如 traceparent、b3、ottrace),同时支持 OpenTelemetry SDK 的 TextMapPropagator 接口规范。
数据同步机制
中间件在 ServeHTTP 入口解析传入 headers,调用 propagator.Extract() 获取 context.Context;响应前通过 propagator.Inject() 回写标准化字段:
func OTelMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header))
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
HeaderCarrier实现TextMapCarrier接口,自动适配 W3C 与 B3 多格式解析;Extract()内部依据 header 前缀智能路由至对应 propagator。
兼容性策略对比
| 传播格式 | 是否默认启用 | 跨语言互通性 | SDK 支持度 |
|---|---|---|---|
traceparent |
✅(OTel 1.0+) | ⭐⭐⭐⭐⭐ | 原生 |
b3 |
❌(需显式注册) | ⭐⭐⭐⭐ | 插件扩展 |
graph TD
A[Incoming HTTP Request] --> B{Header contains traceparent?}
B -->|Yes| C[Use W3C Propagator]
B -->|No, has b3:traceid| D[Use B3 Propagator]
C & D --> E[Unified Context]
4.4 基于OTel Collector的本地开发调试Pipeline搭建与Jaeger后端对接
在本地开发阶段,需构建轻量、可复现的可观测性调试链路。推荐使用 otelcol-contrib 容器化部署,并直连本地 Jaeger(All-in-One)。
快速启动 Jaeger 后端
docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
-p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 \
-p 16686:16686 -p 4317:4317 -p 4318:4318 -p 9411:9411 \
jaegertracing/all-in-one:1.49
该命令暴露 OpenTelemetry gRPC(4317)、HTTP(4318)及 Zipkin(9411)端点,供 Collector 多协议接入。
OTel Collector 配置核心节选
receivers:
otlp:
protocols:
grpc: # 默认监听 4317
http: # 默认监听 4318
processors:
batch: {}
exporters:
jaeger:
endpoint: "host.docker.internal:14250" # Mac/Win Docker Desktop 兼容地址
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [jaeger]
| 组件 | 协议 | 用途 |
|---|---|---|
otlp/grpc |
gRPC | SDK 推送 trace 的首选通道 |
jaeger |
gRPC | 与 Jaeger Collector 通信 |
batch |
内存 | 缓冲并批量发送,降开销 |
graph TD
A[Instrumented App] -->|OTLP/gRPC| B(OTel Collector)
B -->|Jaeger/gRPC| C[Jaeger Collector]
C --> D[Jaeger UI:16686]
第五章:Go生态可观测性基建的统一范式与未来演进
统一数据模型:OpenTelemetry Go SDK 的深度集成实践
在 Uber、TikTok 和字节跳动的中台服务中,已全面采用 go.opentelemetry.io/otel/sdk 替代原生 expvar 与自研埋点框架。典型改造路径包括:将 http.HandlerFunc 封装为 otelhttp.NewHandler,对 database/sql 驱动注入 otelmysql.Driver,并复用 otelmetric.MustNewMeterProvider() 构建指标管道。以下为生产环境中的采样配置片段:
sdktrace.NewSampler(oteltrace.ParentBased(
oteltrace.TraceIDRatioBased(0.01), // 1% 全链路采样
)),
多后端协同架构下的信号路由策略
当 Prometheus、Jaeger 和 Loki 同时作为接收端时,需避免信号重复导出与资源争抢。某电商订单服务采用如下分层路由设计:
| 信号类型 | 主要接收器 | 备份通道 | 采样率 | 存储周期 |
|---|---|---|---|---|
| Trace | Jaeger | Tempo | 5% | 7天 |
| Metrics | Prometheus | VictoriaMetrics | 100% | 90天 |
| Logs | Loki | Elasticsearch | 100% | 30天 |
该策略通过 otel-collector-contrib 的 routingprocessor 实现动态分流,配置中明确指定 trace_id 哈希模值决定是否投递至 Tempo。
eBPF 辅助观测:基于 iovisor/gobpf 的运行时函数级追踪
在 Kubernetes DaemonSet 中部署 Go 应用时,传统 APM 工具难以捕获 GC STW 期间的 goroutine 阻塞根因。团队引入 gobpf 编写内核模块,监听 runtime.mcall 和 runtime.gopark 事件,并将原始栈帧通过 perf ring buffer 推送至用户态聚合器。实测显示,STW 超过 5ms 的事件捕获率从 62% 提升至 98.7%,且 CPU 开销低于 1.3%。
可观测性即代码:Terraform + OpenTelemetry Collector 模板化部署
使用 Terraform 模块统一管理 200+ 个微服务的采集器配置,关键变量定义如下:
variable "service_name" {
description = "服务唯一标识,用于 resource_attributes"
}
variable "env" {
default = "prod"
}
模块自动注入 resource_detector、batchprocessor 和 queued_retry_exporter,并通过 otelcol_config 数据源生成 YAML 配置,实现版本化、可审计、可回滚的可观测性基建交付。
WASM 插件扩展:基于 WebAssembly 的自定义 Span 处理器
为满足金融场景下敏感字段脱敏需求,团队开发了基于 wasmer-go 的 WASM 插件运行时。Span 数据经 wasmprocessor 加载 .wasm 文件后,在沙箱中执行 Rust 编写的脱敏逻辑(如正则替换银行卡号),全程不突破内存隔离边界。插件热加载耗时
分布式上下文传播的渐进式升级路径
遗留系统中混合使用 x-request-id、uber-trace-id 和 traceparent,导致跨语言调用链断裂。通过 otelhttp.WithPropagators 注册复合传播器,并启用 b3 与 w3c 双格式解析,同时在 HTTP middleware 中自动补全缺失字段。灰度发布期间,调用链完整率从 73% 提升至 99.4%。
智能基线告警:Prometheus + PyOD 离群点检测流水线
将 go_goroutines、http_server_duration_seconds_bucket 等指标流式接入 Python 侧 PyOD 模型,每 5 分钟训练一次 Isolation Forest,动态生成异常阈值。上线后误报率下降 64%,首次捕获到因 goroutine 泄漏引发的内存缓慢增长问题,早于 OOM 发生前 47 分钟。
云原生环境下的低开销指标压缩方案
针对高基数标签(如 user_id, tenant_id)导致的 Prometheus 内存暴涨问题,采用 github.com/prometheus/client_golang/prometheus/promauto 结合 exemplar 与 histogram_quantile 近似算法,在保留 P95/P99 可信度的前提下,将 Series 数量压缩 3.8 倍,TSDB 内存占用降低 52%。
