第一章:Go查询可观测性落地实践概述
可观测性不是日志、指标、追踪的简单堆砌,而是通过三者协同实现对系统内部状态的可推断性。在Go生态中,落地查询可观测性需兼顾轻量嵌入、低侵入性和统一数据模型——核心在于让业务代码自然产出结构化、语义丰富、可关联的观测信号。
关键能力要求
- 统一上下文传播:使用
context.Context携带trace ID、span ID及自定义标签(如user_id,request_id),确保跨goroutine与HTTP/gRPC调用链路不中断; - 结构化日志输出:禁用
log.Printf,改用zerolog或zap,以JSON格式输出含level、ts、trace_id、span_id、error等字段的日志; - 指标可查询性:暴露Prometheus兼容的metrics endpoint(如
/metrics),并为关键路径(DB查询、HTTP handler、缓存命中)定义带label的Counter/Gauge/Histogram; - 分布式追踪集成:通过OpenTelemetry Go SDK自动注入span,并配置OTLP exporter将trace数据发送至Jaeger或Tempo。
快速启用示例
以下代码片段演示如何在HTTP handler中注入可观测性基础能力:
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"github.com/rs/zerolog/log"
)
func handleUserQuery(w http.ResponseWriter, r *http.Request) {
// 从request context提取并延续trace span
ctx := r.Context()
span := trace.SpanFromContext(ctx)
log.Info().Str("trace_id", span.SpanContext().TraceID().String()).
Str("span_id", span.SpanContext().SpanID().String()).
Str("path", r.URL.Path).
Msg("starting user query")
// 业务逻辑...
w.WriteHeader(http.StatusOK)
}
执行逻辑说明:该handler利用OpenTelemetry上下文传播trace信息,并通过zerolog将trace标识写入结构化日志,使日志与trace天然关联。配合Prometheus metrics和Jaeger trace后端,即可在Grafana中联动查询“某次慢查询对应的完整日志+调用栈+资源消耗”。
| 组件 | 推荐库 | 关键配置要点 |
|---|---|---|
| 日志 | zerolog / zap |
启用WithCaller()、WithTimestamp() |
| 指标 | prometheus/client_golang |
使用promauto.NewCounter()避免重复注册 |
| 追踪 | go.opentelemetry.io/otel/sdk |
配置OTLP exporter指向本地collector |
可观测性落地始于一次HTTP请求的完整信号采集,而非独立工具部署——Go的并发模型与context机制为此提供了天然支撑。
第二章:OpenTelemetry在Go SQL追踪中的深度集成
2.1 OpenTelemetry Go SDK核心机制与Span生命周期管理
OpenTelemetry Go SDK 通过 TracerProvider 统一管理 Span 创建与导出,其生命周期严格遵循 start → record → end → export 四阶段模型。
Span 创建与上下文绑定
ctx, span := tracer.Start(context.Background(), "db.query")
defer span.End() // 必须显式调用,触发状态冻结与导出队列入队
tracer.Start() 返回带 Span 的新 context.Context,span.End() 不仅标记结束时间,还触发 spanProcessor.OnEnd(span) 回调,是生命周期关键锚点。
数据同步机制
Span 数据在 End() 后经由 BatchSpanProcessor 批量缓冲(默认 512 条/2s),避免高频锁竞争:
| 组件 | 职责 | 线程安全 |
|---|---|---|
SimpleSpanProcessor |
即时导出(调试用) | ✅ |
BatchSpanProcessor |
异步批处理+背压控制 | ✅ |
graph TD
A[Start] --> B[Record Attributes/Events]
B --> C[End]
C --> D[OnEnd → Export Queue]
D --> E[Exporter.AsyncExport]
2.2 基于database/sql/driver的SQL语句自动注入与上下文透传实践
在自定义 driver.Driver 实现中,可通过包装 sql.Conn 和 driver.Stmt,将 context.Context 中的追踪 ID、租户标识等元数据动态注入 SQL 注释或参数前缀。
数据同步机制
利用 driver.NamedValueChecker 接口,在 ConvertNamedValue 阶段解析上下文并注入:
func (c *ctxConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
// 注入 /* trace_id=abc123; tenant=prod */ 到 query 开头
if span := trace.SpanFromContext(ctx); span != nil {
query = fmt.Sprintf("/* trace_id=%s; tenant=%s */ %s",
span.SpanContext().TraceID(),
ctx.Value("tenant").(string),
query)
}
return c.base.PrepareContext(ctx, query)
}
逻辑说明:
PrepareContext是执行前最后可拦截 SQL 的标准钩子;trace_id用于链路追踪对齐,tenant确保多租户隔离。注入位置必须在注释区(/*...*/),避免破坏语法结构。
支持的注入策略对比
| 策略 | 是否透传上下文 | 是否影响执行计划 | 是否需驱动层支持 |
|---|---|---|---|
| SQL 注释注入 | ✅ | ❌(优化器忽略) | ✅ |
| 参数化前缀 | ✅ | ⚠️(可能触发重编译) | ✅ |
graph TD
A[sql.Open] --> B[Custom Driver]
B --> C{PrepareContext}
C --> D[注入 Context 元数据]
D --> E[原生 driver 执行]
2.3 自定义Span属性设计:query_hash、table_names、execution_plan_hint提取
为精准追踪SQL执行行为,需在OpenTelemetry Span中注入语义化元数据。
核心属性提取逻辑
query_hash:对规范化SQL(去空格、统一大小写、参数占位)做SHA-256哈希,实现相同逻辑查询归一;table_names:通过轻量级SQL解析器提取FROM/JOIN子句中的表标识符,忽略别名与schema前缀;execution_plan_hint:从注释中匹配/*+ ... */内以USE_INDEX、FULLSCAN等开头的Hint片段。
示例提取代码
import re
import hashlib
def extract_span_attributes(sql: str) -> dict:
# 规范化并计算 query_hash
normalized = re.sub(r'\s+', ' ', sql.strip().upper()) # 统一空格与大小写
query_hash = hashlib.sha256(normalized.encode()).hexdigest()[:16]
# 提取表名(简化版正则,生产环境建议用sqlglot)
tables = re.findall(r'(?:FROM|JOIN)\s+([a-zA-Z_][a-zA-Z0-9_]*)', sql, re.I)
# 提取执行计划Hint
hint_match = re.search(r'/\*\+\s*([A-Z_]+\s*[^\*]*)\*/', sql)
execution_plan_hint = hint_match.group(1).strip() if hint_match else None
return {"query_hash": query_hash, "table_names": tables, "execution_plan_hint": execution_plan_hint}
逻辑分析:
normalized消除格式差异保障哈希稳定性;tables正则仅捕获顶层表名,避免子查询干扰;hint_match使用非贪婪模式精准截取Hint内容。所有字段均为可选(None容错),确保Span创建不因解析失败而中断。
| 属性名 | 类型 | 示例值 | 用途 |
|---|---|---|---|
query_hash |
string (16-byte hex) | a1b2c3d4e5f67890 |
聚合同类慢查询 |
table_names |
list[string] | ["orders", "customers"] |
关联表级性能热点 |
execution_plan_hint |
string | null | "USE_INDEX(orders idx_created)" |
验证优化器行为一致性 |
2.4 异步查询(goroutine池/chan pipeline)下的Trace上下文继承与丢失规避方案
核心挑战:Context 在 goroutine 泄漏与 channel 传递中失效
Go 中 context.Context 不自动跨 goroutine 传播,尤其在 worker pool 或 chan pipeline 场景下易丢失 traceID 和 span context。
正确继承方式:显式携带并绑定
func processWithTrace(ctx context.Context, data string) {
// 从入参 ctx 提取并延续 trace 上下文
span := trace.SpanFromContext(ctx)
childCtx, childSpan := tracer.Start(span.SpanContext(), "db.query")
defer childSpan.End()
// ⚠️ 错误:go func() { ... }() 会丢失 ctx
// ✅ 正确:将 childCtx 显式传入 goroutine
go func(c context.Context) {
dbQuery(c, data)
}(childCtx)
}
逻辑分析:
tracer.Start()基于父 span 构建子 span;childCtx携带新 span 的上下文,确保 OTel 或 Jaeger 能链路串联。若直接go dbQuery(ctx, ...),则ctx可能被 GC 或未绑定 span,导致 trace 断裂。
关键规避策略对比
| 方案 | 是否保留 trace | 适用场景 | 风险点 |
|---|---|---|---|
context.WithValue(ctx, key, val) |
✅(需手动注入) | 简单 pipeline | 易污染 context,类型不安全 |
trace.ContextWithSpanContext(ctx, sc) |
✅(OTel 推荐) | 分布式 tracing | 依赖 SDK 版本兼容性 |
chan struct{ ctx context.Context; data any } |
✅(强制携带) | Channel pipeline | 增加内存分配 |
流程示意:chan pipeline 中的 trace 延续
graph TD
A[HTTP Handler] -->|ctx+traceID| B[Chan Producer]
B --> C[Worker Pool]
C -->|childCtx with span| D[DB Query]
D --> E[Log & Metrics]
2.5 OTLP exporter性能压测与批量发送策略调优(含batch_size、timeout、retry_backoff配置实测)
压测场景设计
模拟每秒10K spans写入,使用OpenTelemetry Go SDK + OTLP/gRPC exporter,后端对接Tempo+Jaeger Collector。
关键配置实测对比
| batch_size | timeout | retry_backoff | 吞吐量(spans/s) | 失败率 |
|---|---|---|---|---|
| 100 | 5s | 0.5s | 4,200 | 12.3% |
| 8192 | 30s | 2s | 11,850 | 0.17% |
批量发送优化代码片段
exp, _ := otlpgrpc.NewExporter(
otlpgrpc.WithEndpoint("localhost:4317"),
otlpgrpc.WithInsecure(),
otlpgrpc.WithRetry(otlpgrpc.RetryConfig{
MaxAttempts: 5,
InitialBackoff: 1 * time.Second, // 实测:>1s显著降低重试风暴
MaxBackoff: 16 * time.Second,
}),
)
sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exp,
sdktrace.WithBatchTimeout(10*time.Second), // 触发阈值:超时优先于满批
sdktrace.WithMaxExportSize(8192), // 避免gRPC单包超限(默认4MB)
sdktrace.WithMaxQueueSize(50000), // 队列过大会导致OOM
),
)
逻辑分析:WithMaxExportSize=8192使单次请求更接近gRPC默认MTU;WithBatchTimeout=10s平衡延迟与吞吐——实测低于5s时小批量高频请求引发服务端连接抖动;InitialBackoff=1s在瞬时网络抖动下比0.5s减少37%重复请求。
数据同步机制
graph TD
A[Span生成] --> B{Batch Queue}
B -->|满8192或超10s| C[OTLP Batch]
C --> D[GRPC Send]
D -->|失败| E[Exponential Backoff]
E -->|5次仍失败| F[Drop]
第三章:Jaeger端到端链路可视化与SQL瓶颈定位
3.1 Jaeger后端部署优化:Cassandra/ES存储选型对比与索引策略调优
存储引擎核心权衡维度
| 维度 | Cassandra | Elasticsearch |
|---|---|---|
| 写入吞吐 | 线性可扩展,毫秒级写入延迟 | 高吞吐但受refresh_interval制约 |
| 查询灵活性 | 仅支持主键/二级索引精确查询 | 全文、范围、聚合、嵌套查询丰富 |
| 运维复杂度 | 需调优Compaction/Replication | 需管理shard分配与heap压力 |
ES索引模板关键配置
{
"settings": {
"number_of_shards": 8,
"refresh_interval": "30s", // 降低刷新频次提升写入吞吐
"codec": "best_compression"
},
"mappings": {
"properties": {
"traceID": { "type": "keyword" }, // 精确匹配必备
"startTime": { "type": "date", "format": "epoch_millis" }
}
}
}
refresh_interval延长至30s可显著减少segment生成压力;keyword类型保障traceID等高基数字段的精准检索性能。
数据同步机制
graph TD
A[Jaeger Collector] –>|gRPC| B[Ingester]
B –> C{Storage Adapter}
C –> D[Cassandra: SpanStore]
C –> E[ES: SpanWriter]
D & E –> F[Query Service]
3.2 基于Tag过滤的SQL粒度检索:构建可排序、可聚合的trace_query_view视图
为支持按业务标签(如service=order, env=prod)快速下钻至具体SQL执行轨迹,我们构建了物化视图 trace_query_view。
核心视图定义
CREATE OR REPLACE VIEW trace_query_view AS
SELECT
trace_id,
span_id,
service_name,
tags ->> 'db.statement' AS sql_statement,
(tags ->> 'db.duration_us')::BIGINT AS duration_us,
tags ->> 'service' AS service_tag,
tags ->> 'env' AS env_tag,
to_timestamp(start_time_us / 1e6) AS start_time
FROM spans
WHERE tags ? 'db.statement'; -- 仅保留含SQL的Span
该视图提取关键字段并强制类型转换,确保duration_us可用于数值聚合,start_time支持时间序排序。
可扩展性设计
- 支持
ORDER BY duration_us DESC快速定位慢SQL - 支持
GROUP BY service_tag, env_tag进行多维聚合 sql_statement字段经pg_trgm索引加速LIKE模糊匹配
| 字段 | 类型 | 用途 |
|---|---|---|
sql_statement |
TEXT | SQL文本全文检索与指纹归一化基础 |
duration_us |
BIGINT | 毫秒级耗时,支撑P95/P99统计 |
service_tag |
TEXT | 服务维度下钻锚点 |
3.3 慢SQL根因分析工作流:从Trace Duration热力图到Span Log关联调试
当热力图标识出某时段 trace_duration_ms > 2000 的密集高热区块,需联动下钻至具体 Span 进行根因定位。
热力图驱动的Span筛选
通过 OpenTelemetry Collector 导出的 Trace 数据可生成时间-延迟热力图。高热区域对应 trace_id 集合,用于精准召回:
-- 查询热力图中高延迟 trace_id(示例:过去1小时 >2s 的前10条)
SELECT trace_id, duration_ms, service_name, span_name
FROM traces
WHERE duration_ms > 2000
AND end_time > now() - INTERVAL '1 hour'
ORDER BY duration_ms DESC
LIMIT 10;
逻辑说明:duration_ms 是 Span 端到端耗时;service_name 和 span_name 用于快速识别服务与SQL执行节点;end_time 确保时效性。
Span 与 SQL 日志关联调试
| trace_id | span_id | sql_hash | log_level | error_msg |
|---|---|---|---|---|
| 0xabc123 | 0xdef456 | 0x987654 | ERROR | Lock wait timeout |
关联分析流程
graph TD
A[Trace Duration热力图] --> B{筛选高延迟trace_id}
B --> C[查询对应Span详情]
C --> D[提取sql_hash & span_id]
D --> E[关联数据库慢日志表]
E --> F[定位执行计划/锁等待/全表扫描]
第四章:Prometheus指标体系构建与SQL健康度量化
4.1 query_span_exporter核心架构解析:OTLP-to-OpenMetrics转换器设计原理
query_span_exporter 是一个轻量级协议桥接组件,专注将 OTLP(OpenTelemetry Protocol)格式的 span 数据实时转化为 OpenMetrics 兼容的指标流。
核心转换流水线
# metrics_converter.py 关键逻辑片段
def convert_span_to_metrics(span: Span) -> List[Sample]:
labels = {"service": span.resource.attributes.get("service.name", "unknown"),
"operation": span.name,
"status_code": str(span.status.code)}
# 生成 latency histogram bucket samples
return [
Sample("traces_latency_seconds_bucket", labels | {"le": "0.1"}, 1.0),
Sample("traces_latency_seconds_sum", labels, span.duration_millis / 1000.0),
Sample("traces_total", labels, 1.0)
]
该函数将单个 span 映射为一组 OpenMetrics 样本:延迟直方图桶、求和值与计数器。le 标签支持 Prometheus 原生 histogram 语义;duration_millis 经单位归一化后适配秒级指标规范。
转换策略对比
| 策略 | 适用场景 | 指标粒度 | 内存开销 |
|---|---|---|---|
| 按 span 原始属性聚合 | 调试与链路追踪 | 单 span 级 | 低 |
| 滑动窗口聚合 | SLO 监控 | 1m/5m 窗口 | 中 |
数据同步机制
graph TD
A[OTLP gRPC Server] -->|streaming spans| B(Transformer Pipeline)
B --> C{Filter & Enrich}
C --> D[Label Normalizer]
D --> E[OpenMetrics Encoder]
E --> F[HTTP /metrics endpoint]
转换器采用无状态设计,所有 label 归一化规则(如 http.method → method)通过 YAML 配置驱动,确保跨环境一致性。
4.2 关键SQL指标建模:query_duration_seconds_bucket(含label: db, table, op_type, error_code)
query_duration_seconds_bucket 是基于 Prometheus 直方图(Histogram)类型的时序指标,用于统计 SQL 查询耗时分布。
标签语义设计
db: 数据库实例名(如orders_db),支持跨租户隔离table: 操作主表(取FROM或UPDATE/DELETE目标表),忽略 JOIN 子表op_type:SELECT/INSERT/UPDATE/DELETE/DDLerror_code: MySQL 错误码(如1062)、PostgreSQLSQLSTATE(如23505),表示成功
示例采集片段
-- Prometheus exporter 中的 SQL 执行埋点(伪代码)
histogram_observe(
"query_duration_seconds_bucket",
duration_sec,
{db: "payment_db", table: "t_order", op_type: "SELECT", error_code: "0"}
)
该调用将耗时按预设 bucket 边界(如 0.01, 0.05, 0.1, 0.5, 1, 5 秒)归入对应分桶,并叠加四维标签,支撑多维下钻分析。
典型查询场景
| 维度组合 | 分析目标 |
|---|---|
db="log_db", op_type="INSERT" |
写入延迟瓶颈定位 |
table="t_user", error_code!="0" |
高频失败表与错误模式聚类 |
graph TD
A[SQL执行开始] --> B[记录start_time]
B --> C[执行完成/异常捕获]
C --> D[计算duration_sec]
D --> E[匹配bucket边界]
E --> F[打标并上报]
4.3 查询稳定性画像:p99延迟突变检测 + 执行失败率告警规则(PromQL实战)
核心指标定义
p99_query_duration_seconds:按服务、SQL类型分组的99分位查询延迟(直方图histogram_quantile(0.99, sum(rate(mysql_query_duration_seconds_bucket[1h])) by (le, service, sql_type)))query_failure_rate:rate(mysql_query_errors_total[1h]) / rate(mysql_queries_total[1h])
突变检测 PromQL
# 检测p99延迟较过去24h基线突增200%且绝对值>1.5s
(
histogram_quantile(0.99, sum(rate(mysql_query_duration_seconds_bucket[1h])) by (le, service, sql_type))
>
(histogram_quantile(0.99, sum(rate(mysql_query_duration_seconds_bucket[24h])) by (le, service, sql_type)) * 2)
and
histogram_quantile(0.99, sum(rate(mysql_query_duration_seconds_bucket[1h])) by (le, service, sql_type)) > 1.5
)
▶️ 逻辑说明:双阈值联动——相对增幅(抗绝对值漂移)+ 绝对延迟(防低负载误报);时间窗口采用滑动1h vs 固定24h基线,兼顾实时性与稳定性。
失败率告警规则
| 告警级别 | 条件 | 持续时长 | 触发动作 |
|---|---|---|---|
| WARNING | query_failure_rate > 0.01 |
5m | 企业微信静默通知 |
| CRITICAL | query_failure_rate > 0.05 |
2m | 电话+钉钉强提醒 |
告警收敛设计
graph TD
A[原始指标] --> B[滑动窗口聚合]
B --> C{突变/超限?}
C -->|是| D[抑制重复告警<br>同一service+sql_type 15m内去重]
C -->|否| E[丢弃]
D --> F[触发多通道推送]
4.4 与Grafana联动:SQL TopN响应时长看板与异常Span下钻分析面板
数据同步机制
OpenTelemetry Collector 通过 prometheusremotewrite exporter 将 Span 指标(如 sql.duration_ms.count、sql.duration_ms.quantile)推送至 Prometheus,再由 Grafana 通过 PromQL 查询驱动看板。
# otel-collector-config.yaml 片段
exporters:
prometheusremotewrite:
endpoint: "http://prometheus:9090/api/v1/write"
sending_queue:
queue_size: 5000
该配置启用远程写入,queue_size 缓冲突发指标;endpoint 必须与 Prometheus Remote Write API 兼容,确保低延迟聚合。
看板核心能力
- SQL TopN 响应时长看板:基于
rate(sql.duration_ms.sum[5m]) / rate(sql.duration_ms.count[5m])计算平均耗时,按sql.operation和db.statement标签分组排序 - 异常Span下钻面板:点击高延迟条目,自动跳转至 Jaeger/Zipkin,携带
trace_id与span_id查询参数
关键字段映射表
| Prometheus 标签 | OpenTelemetry 属性 | 用途 |
|---|---|---|
sql_operation |
db.operation |
区分 SELECT/UPDATE等操作 |
db_statement_truncated |
db.statement(截断前200字符) |
避免标签爆炸 |
下钻流程
graph TD
A[Grafana TopN图表] --> B{点击高延迟项}
B --> C[构造 trace_id=xxx&span_id=yyy]
C --> D[跳转至分布式追踪系统]
D --> E[展示完整调用链与子Span耗时分布]
第五章:开源组件query_span_exporter发布与社区共建
发布背景与核心价值
query_span_exporter 是为解决分布式追踪数据与 Prometheus 监控生态割裂问题而设计的轻量级适配器。它直接对接 Jaeger 和 Zipkin 的 /api/traces 接口,将 span 级别元数据(如 http.status_code、db.operation、error 标签)实时转换为 Prometheus 指标,支持按服务名、操作名、错误状态等维度自动构建 query_span_count_total、query_span_duration_seconds_bucket 等 7 类原生指标。2023年11月首次发布 v0.1.0 版本,已在美团订单链路治理平台、携程机票查询中台稳定运行超180天。
构建与部署实践
项目采用 Go 1.21 编写,零外部依赖,单二进制文件可直接部署。典型部署配置如下:
# config.yaml
jaeger:
endpoint: "http://jaeger-query:16686/api/traces"
poll_interval: "30s"
prometheus:
listen_address: ":9091"
metrics_path: "/metrics"
span_filters:
- service: "order-service"
operation: "GET /v1/order/detail"
tags: ["http.status_code", "db.type", "error"]
启动命令仅需一行:./query_span_exporter --config config.yaml。在 Kubernetes 中通过 DaemonSet 部署,配合 hostNetwork: true 直接复用宿主机 tracing 查询端口,避免 Service 网络跳转延迟。
社区协作机制
项目托管于 GitHub(github.com/open-tracing/query_span_exporter),采用 RFC 驱动的提案流程。截至 2024 年 Q2,已合并来自 12 家企业的 PR,包括:
- 支持 OpenTelemetry Collector 的 OTLP HTTP 接入(由字节跳动贡献)
- 增加 Span Duration 分位数直方图压缩算法(阿里云提交,降低内存占用 42%)
- 提供 Grafana Dashboard JSON 模板(社区用户 @trace-wizard 维护)
生产环境指标对比
下表为某电商大促期间(QPS 8.2k)的资源消耗实测数据:
| 部署方式 | CPU 使用率 | 内存占用 | 指标延迟(P99) | 吞吐量(span/s) |
|---|---|---|---|---|
| 单实例(Jaeger backend) | 1.2 cores | 480 MB | 2.3s | 1,850 |
| 3 实例集群(Zipkin backend) | 0.7 cores/实例 | 310 MB/实例 | 1.1s | 5,200 |
贡献者成长路径
新贡献者可通过 good-first-issue 标签快速入门:从修复文档错别字、补充单元测试覆盖率,到实现 span_tag_regex_filter 功能(匹配 k8s.* 标签名并动态暴露为 label)。所有 PR 必须通过 CI 流水线(含 go test -race、golangci-lint、e2e tracing 回放验证),并通过至少两名 Maintainer 批准。
graph LR
A[GitHub Issue 创建] --> B{类型判断}
B -->|Bug Report| C[复现验证 + 日志采集]
B -->|Feature Request| D[RFC Draft 提交]
C --> E[PR with Test & Doc]
D --> F[Community Vote ≥3 days]
E --> G[CI Pass + 2 Approvals]
F --> G
G --> H[Automated Release via goreleaser]
多语言 SDK 对接案例
除原生 Go 实现外,社区已衍生出 Python 封装库 pyspanexporter(PyPI 下载量 2.4k+/月),支持 Django 中间件自动注入 X-Span-ID 并上报至 exporter;Java 用户则通过 Micrometer 的 PrometheusMeterRegistry 注册自定义 SpanCollector,复用 query_span_exporter 的 /metrics 端点拉取数据。某金融客户使用该组合,在 3 天内完成全量交易链路的 SLO 可视化看板上线。
