第一章:Go微服务预订链路追踪失效的典型现象与根因定位
在高并发预订场景下,链路追踪系统常出现“断链”现象:前端请求发起后,Jaeger 或 Zipkin 仪表盘仅显示入口服务(如 API Gateway)的 Span,后续服务(如 Inventory、Payment、Booking)无任何 Span 上报,或 Span 间缺少 parent_id 关联,导致完整调用链断裂为孤立节点。
典型失效现象
- 请求日志中存在
trace_id,但下游服务日志缺失该trace_id - OpenTelemetry SDK 报错:
failed to inject span context: context cancelled - HTTP Header 中缺失
traceparent或uber-trace-id字段 - gRPC 调用中
metadata.MD未透传 trace context
根因高频场景
- HTTP 客户端未集成追踪中间件:原生
http.Client默认不传播 context,需显式注入 - goroutine 泄漏导致 context 丢失:异步处理(如
go func() { ... }())未传递ctx,Span 生命周期早于 goroutine 结束 - 中间件注册顺序错误:Gin/Echo 中 tracing middleware 位于
Recovery()之后,panic 导致 Span 未结束即被丢弃
快速验证与修复步骤
检查 HTTP 客户端是否正确注入 trace context:
// ✅ 正确:使用 otelhttp.RoundTripper 包装 client
client := &http.Client{
Transport: otelhttp.NewTransport(http.DefaultTransport),
}
req, _ := http.NewRequestWithContext(ctx, "POST", "http://inventory-svc/v1/check", nil)
// otelhttp 自动从 ctx 提取 SpanContext 并写入 req.Header["traceparent"]
resp, err := client.Do(req)
验证 context 是否在 goroutine 中延续:
// ❌ 错误:丢失 ctx
go func() {
inventory.Check(ctx) // 实际未使用传入的 ctx
}()
// ✅ 正确:显式传递并继承
go func(ctx context.Context) {
inventory.Check(ctx) // 确保下游 Span 关联父 Span
}(ctx)
常见配置疏漏对照表
| 组件 | 易错点 | 推荐方案 |
|---|---|---|
| Gin Router | tracing middleware 注册位置靠后 | 在 Use() 中首个注册 |
| gRPC Client | 未调用 metadata.AppendToOutgoingContext |
使用 otelgrpc.WithPropagators |
| 日志埋点 | 直接打印 span.SpanContext().TraceID() 而非通过 logrus/zerolog 的 trace hook |
启用 otellogrus.Hook |
第二章:OpenTelemetry在Go微服务中的标准化接入实践
2.1 OpenTelemetry SDK初始化与全局TracerProvider配置
OpenTelemetry SDK 初始化是可观测性能力落地的基石,其核心在于构建并注册全局 TracerProvider,确保所有 Tracer 实例共享统一的导出、采样与资源配置。
全局 TracerProvider 注册示例
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessor
from opentelemetry.sdk.resources import Resource
# 构建带资源标识的 TracerProvider
provider = TracerProvider(
resource=Resource.create({"service.name": "auth-service"}),
sampler=trace.sampling.ALWAYS_ON,
)
# 配置控制台导出器(开发调试用)
processor = BatchSpanProcessor(ConsoleSpanExporter())
provider.add_span_processor(processor)
# 设置为全局实例 —— 后续 trace.get_tracer() 将自动使用它
trace.set_tracer_provider(provider)
逻辑分析:
trace.set_tracer_provider()是单次幂等操作,首次调用即锁定全局TracerProvider;Resource定义服务元数据,影响后端识别;BatchSpanProcessor提供异步批量导出能力,避免阻塞业务线程。
关键配置项对比
| 配置项 | 生产推荐值 | 说明 |
|---|---|---|
sampler |
ParentBased(TraceIdRatioBased(0.01)) |
平衡精度与开销,支持动态调整 |
span_limits |
显式设限(如 max_attributes=128) |
防止 span 膨胀导致内存溢出 |
shutdown_on_exit |
True(需配合 atexit) |
确保进程退出前 flush 剩余 spans |
graph TD
A[应用启动] --> B[初始化 TracerProvider]
B --> C{是否已设置全局 Provider?}
C -->|否| D[调用 trace.set_tracer_provider]
C -->|是| E[忽略重复设置]
D --> F[Tracer 实例自动绑定]
2.2 Go HTTP中间件自动注入Span上下文的原理与定制化改造
Go 的 http.Handler 中间件通过包装 http.ResponseWriter 和 *http.Request 实现链式调用,OpenTracing 或 OpenTelemetry SDK 利用此机制在 ServeHTTP 入口自动提取并注入 Span 上下文。
核心注入时机
- 请求进入时从
X-B3-TraceId等 header 解析传播上下文 - 创建子 Span 并绑定至
context.Context - 将带 Span 的 context 注入
*http.Request
自定义注入点示例
func TracingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 从请求头提取 trace 上下文(支持 B3、W3C TraceContext)
ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header))
// 创建 span 并注入新 context
ctx, span := tracer.Start(ctx, r.URL.Path, trace.WithSpanKind(trace.SpanKindServer))
defer span.End()
// 将带 span 的 context 注入 request
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
此代码将
otel.GetTextMapPropagator().Extract()用于标准传播器解析;tracer.Start()创建服务端 Span;r.WithContext()完成上下文透传,确保后续 handler 可通过r.Context()获取活跃 Span。
支持的传播格式对比
| 格式 | Header 示例 | 是否默认启用 | 备注 |
|---|---|---|---|
| W3C TraceContext | traceparent: 00-... |
✅(OTel SDK 默认) | 推荐,标准化程度高 |
| B3 Single Header | b3: 80f198ee56343ba864fe8b2a57d3eff7-... |
❌(需显式配置) | 兼容 Zipkin 生态 |
graph TD
A[HTTP Request] --> B{Extract from Headers}
B --> C[W3C/B3 Propagator]
C --> D[Create Server Span]
D --> E[Inject into r.Context()]
E --> F[Next Handler]
2.3 gRPC拦截器中Span传播机制实现与Context透传验证
gRPC拦截器是实现分布式链路追踪的关键切面。Span传播依赖于Metadata在客户端与服务端间的双向透传。
拦截器注入Trace上下文
func clientInterceptor(ctx context.Context, method string, req, reply interface{},
cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// 从当前context提取span并写入metadata
span := trace.SpanFromContext(ctx)
md, _ := metadata.FromOutgoingContext(ctx)
md = metadata.Join(md, trace propagation.HeaderCarrier{
Metadata: md,
TraceID: span.SpanContext().TraceID().String(),
SpanID: span.SpanContext().SpanID().String(),
TraceFlags: span.SpanContext().TraceFlags().String(),
}.ToMetadata())
ctx = metadata.NewOutgoingContext(ctx, md)
return invoker(ctx, method, req, reply, cc, opts...)
}
该拦截器将当前Span的TraceID、SpanID和TraceFlags序列化为binary/grpc-trace-bin格式写入Metadata,确保跨进程传递。
服务端Context还原流程
graph TD
A[收到RPC请求] --> B[解析Metadata中的trace-bin]
B --> C[构建SpanContext]
C --> D[创建server span]
D --> E[注入新context]
关键传播字段对照表
| 字段名 | 类型 | 用途 |
|---|---|---|
trace-id |
string | 全局唯一链路标识 |
span-id |
string | 当前Span局部唯一标识 |
traceflags |
hex | 是否采样(0x01=sampled) |
2.4 异步任务(goroutine/worker pool)中Span上下文丢失场景复现与修复方案
场景复现:goroutine 中 Context 携带失效
Go 默认不自动传播 context.Context 到新 goroutine,若 Span(如 OpenTelemetry 的 SpanContext)仅存于父 context,直接 go fn() 将导致子协程无法访问追踪上下文。
func handleRequest(ctx context.Context, w http.ResponseWriter, r *http.Request) {
ctx, span := tracer.Start(ctx, "http-handler")
defer span.End()
go func() { // ❌ 丢失 ctx & span
processAsync(ctx) // ctx 未携带 span,otel.SpanFromContext(ctx) == nil
}()
}
逻辑分析:
go启动的匿名函数虽接收ctx参数,但若processAsync内部未显式调用otel.ContextWithSpan(ctx, span)或未通过trace.WithSpan包装,SpanFromContext将返回空 Span。根本原因是context.WithValue的键值对在 goroutine 间不自动继承,需手动传递。
修复方案对比
| 方案 | 是否保留 Span | 是否需修改业务逻辑 | 风险点 |
|---|---|---|---|
手动传参 ctx + span |
✅ | ✅(显式传入) | 易遗漏,侵入性强 |
使用 context.WithValue(ctx, key, span) + SpanFromContext |
✅ | ⚠️(需统一 key) | 键冲突、类型断言失败 |
基于 worker pool 的 context.Context 绑定初始化 |
✅ | ❌(封装在池层) | 需定制化 Pool |
推荐实践:带上下文感知的 Worker Pool
type TracedWorkerPool struct {
pool *workerpool.Pool
}
func (p *TracedWorkerPool) Submit(ctx context.Context, f func(context.Context)) {
p.pool.Submit(func() { f(ctx) }) // ✅ 父 ctx 安全传递
}
此方式将上下文传播逻辑收口至池层,业务侧无感知,避免 Span 泄漏。
2.5 跨服务调用中TraceID/Metrics/Baggage三元组一致性保障策略
在分布式链路追踪中,TraceID标识请求全局唯一性,Metrics(如延迟、状态码)反映服务健康度,Baggage携带业务上下文(如tenant_id、ab_test_group)。三者需在跨服务传递中严格同步,否则将导致监控失真与诊断断点。
数据同步机制
采用 OpenTelemetry SDK 的 propagator 统一注入与提取,确保三元组随 HTTP Header 透传:
# 示例:自定义 Baggage 注入器(兼容 W3C TraceContext)
from opentelemetry.propagators import CompositePropagator
from opentelemetry.propagators.b3 import B3MultiFormat
from opentelemetry.propagators.textmap import TextMapPropagator
propagator = CompositePropagator(
[B3MultiFormat(), TextMapPropagator()] # 同时支持 B3 和 W3C 标准
)
逻辑分析:
CompositePropagator允许多协议共存,避免网关/旧服务因协议不兼容导致TraceID丢失;TextMapPropagator确保Baggage键值对(如env=prod)被原样保留于baggageheader 中。
一致性校验流程
graph TD
A[入口服务] -->|注入 TraceID + Baggage + Metrics采样标记| B[HTTP Client]
B --> C[下游服务]
C -->|校验三者是否同源| D[Metrics Collector]
D -->|异常时触发告警| E[统一可观测平台]
| 组件 | 关键约束 |
|---|---|
| TraceID | 全链路只生成一次,禁止重写 |
| Baggage | 仅允许追加,禁止覆盖已有 key |
| Metrics 上报 | 必须绑定当前 span 的 trace_id 和 baggage |
第三章:Jaeger后端集成与链路数据可视化调优
3.1 Jaeger Agent与Collector高可用部署模式对比与Go客户端适配
Jaeger 的可观测性链路保障依赖于 Agent 与 Collector 的协同与容错能力。二者在高可用设计上存在本质差异:
- Agent:轻量级边车(sidecar),无状态,通过 DNS 轮询或静态列表发现多个 Collector;
- Collector:有状态服务端,需配合后端存储(如 Cassandra/Elasticsearch)与负载均衡器(如 Nginx/Envoy)实现水平扩展。
| 维度 | Agent 高可用 | Collector 高可用 |
|---|---|---|
| 发现机制 | DNS SRV / static endpoints | Service Mesh 或 VIP + Health Check |
| 故障转移 | 自动重试下一 Collector | 依赖上游 LB 转发,自身无跨实例同步 |
| 数据可靠性 | 本地 UDP 缓冲(可丢包) | 接收即写入后端,支持幂等与重试队列 |
数据同步机制
Agent 不同步数据,仅转发;Collector 间无对等通信,依赖外部存储作为唯一真相源。
// Go 客户端配置多 Collector 地址(Agent 模式下透明)
cfg := config.Configuration{
Sampler: &config.SamplerConfig{Type: "const", Param: 1},
Reporter: &config.ReporterConfig{
LocalAgentHostPort: "jaeger-agent:6831", // UDP endpoint
CollectorEndpoint: "http://jaeger-collector-headless:14268/api/traces",
},
}
该配置使客户端优先走 Agent(降低延迟),Fallback 至 Collector HTTP 接口(提升可靠性)。LocalAgentHostPort 启用 UDP 批量上报,CollectorEndpoint 提供直连兜底路径,参数控制采样与传输策略。
3.2 Trace采样率动态调控与业务关键路径精准捕获实践
在高并发微服务场景下,固定采样率易导致关键链路漏采或非核心链路冗余采集。我们基于业务标签(如 pay_type=VIP、scene=order_submit)与实时QPS反馈,实现采样率毫秒级自适应调整。
动态采样策略引擎
def calculate_sample_rate(span: Span) -> float:
if span.tags.get("is_critical") == "true": # 业务关键标识
return 1.0 # 全量捕获
base = 0.01 # 基线采样率
qps = get_service_qps(span.service_name)
return min(0.5, base * (1 + qps / 1000)) # QPS越高,适度提升采样
逻辑分析:优先保障带 is_critical 标签的Span全量上报;对普通Span,以QPS为杠杆线性调节,上限封顶防压垮链路系统。
关键路径识别规则表
| 触发条件 | 采样率 | 生效范围 |
|---|---|---|
http.status_code == 5xx |
1.0 | 当前Span及上下游1跳 |
db.operation == 'UPDATE' AND db.table == 'orders' |
0.8 | 仅本Span |
流量调控流程
graph TD
A[Span进入] --> B{含critical标签?}
B -->|是| C[rate=1.0]
B -->|否| D[查QPS+业务规则]
D --> E[计算动态rate]
E --> F[执行采样决策]
3.3 自定义Tag注入与语义约定(Semantic Conventions)落地指南
核心原则:先约定,后注入
OpenTelemetry 语义约定(v1.22+)要求业务 Tag 必须区分 standard(如 http.status_code)与 custom(如 biz.order_type),且 custom 前缀需统一为 app. 或组织域名反写(如 cn.example.billing_stage)。
注入实践示例
from opentelemetry import trace
from opentelemetry.trace import SpanKind
span = trace.get_current_span()
# ✅ 合规注入:自定义Tag带命名空间,值类型明确
span.set_attribute("app.order_id", "ORD-7890") # string
span.set_attribute("app.retry_count", 3) # int
span.set_attribute("app.is_premium", True) # bool
逻辑分析:
set_attribute自动序列化基础类型;app.前缀确保与 OTel 标准 Tag 隔离,避免冲突;布尔/整型直传避免字符串误解析。
推荐属性映射表
| 场景 | 推荐 Key | 类型 | 示例值 |
|---|---|---|---|
| 订单来源 | app.source_channel |
string | "wechat_mini" |
| 业务处理阶段 | app.process_phase |
string | "validation" |
| 灰度标识 | app.release_tag |
string | "v2.3-beta" |
数据同步机制
graph TD
A[应用代码注入 app.* Tag] --> B[OTel SDK 批量导出]
B --> C[Collector 按语义规则路由]
C --> D[后端按 app.* 建索引 & 聚合]
第四章:自定义Span上下文注入的深度定制与全链路贯通
4.1 基于context.WithValue的轻量级Span上下文封装与生命周期管理
在分布式追踪中,Span需随请求链路透传且严格绑定请求生命周期。context.WithValue 提供了无侵入、零依赖的上下文携带能力,是轻量级封装的理想基座。
核心封装模式
// 定义类型安全的key,避免字符串key冲突
type spanContextKey struct{}
// 将span注入context(仅一次,不可变)
ctx = context.WithValue(parentCtx, spanContextKey{}, span)
// 安全提取span(需类型断言)
if s, ok := ctx.Value(spanContextKey{}).(trace.Span); ok {
s.AddEvent("processing")
}
逻辑分析:使用未导出结构体作key确保唯一性;
WithValue返回新context,原context不变,天然支持goroutine安全;Span生命周期完全由context取消机制(WithCancel/WithTimeout)自动管理。
生命周期对齐策略
| 场景 | context行为 | Span状态 |
|---|---|---|
| HTTP请求开始 | WithTimeout 创建 |
Start() |
| 中间件/Handler执行 | 沿用父context | 活跃中 |
| 请求结束/超时触发 | context.Done()关闭 | 自动Finish() |
数据同步机制
graph TD
A[HTTP Handler] --> B[WithSpanCtx]
B --> C[DB Query]
C --> D[RPC Call]
D --> E[context.Cancel]
E --> F[Span.Finish]
4.2 订单预订核心链路(下单→库存预占→支付回调→履约分发)Span命名与层级建模
为精准追踪跨服务调用时序与依赖,需对核心链路各阶段定义语义化 Span 名称,并构建清晰的父子层级关系。
Span 命名规范
order:create(根 Span,下单入口)inventory:reserve(子 Span,库存预占)payment:callback(子 Span,异步支付回调)fulfillment:dispatch(子 Span,履约分发)
层级建模示例(Mermaid)
graph TD
A[order:create] --> B[inventory:reserve]
A --> C[payment:callback]
C --> D[fulfillment:dispatch]
关键 Span 属性注入代码
// 在下单 Controller 中创建根 Span
Span root = tracer.spanBuilder("order:create")
.setParent(Context.current()) // 显式继承上下文
.setAttribute("order_id", orderId)
.setAttribute("user_id", userId)
.startSpan();
逻辑分析:order:create 作为根 Span,承载业务主键(order_id)、用户标识(user_id)等关键维度,供后续链路过滤与下钻;setParent(Context.current()) 确保在无显式传参场景下仍能延续 Trace 上下文。
| 阶段 | Span 名称 | 是否必须为子 Span | 关键属性 |
|---|---|---|---|
| 下单 | order:create |
否(根 Span) | order_id, channel |
| 库存预占 | inventory:reserve |
是 | sku_id, quantity, reserve_result |
| 支付回调 | payment:callback |
是 | pay_order_id, status, notify_time |
4.3 中间件+业务代码双视角Span上下文注入:从gin.HandlerFunc到领域事件处理器
在分布式追踪中,Span上下文需贯穿 HTTP 请求生命周期与异步领域事件处理链路。
Gin 中间件注入 TraceID
func TracingMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 从 HTTP Header 提取 traceparent 或生成新 Span
ctx := otel.GetTextMapPropagator().Extract(
c.Request.Context(),
propagation.HeaderCarrier(c.Request.Header),
)
span := trace.SpanFromContext(ctx)
c.Set("span", span) // 注入至 Gin 上下文
c.Next()
}
}
逻辑分析:propagation.HeaderCarrier 将 c.Request.Header 转为传播载体;Extract 恢复上游 Span 上下文;c.Set("span") 使后续 Handler 可安全访问 Span 实例。
领域事件处理器延续上下文
| 组件 | 上下文来源 | 注入方式 |
|---|---|---|
| Gin Handler | HTTP Header | c.Request.Context() |
| Event Handler | c.MustGet("span") |
trace.ContextWithSpan() |
数据同步机制
func HandleUserCreatedEvent(ctx context.Context, evt UserCreated) {
// 基于 Gin 注入的 span 构建子 Span
_, span := tracer.Start(
trace.ContextWithSpan(ctx, c.MustGet("span").(trace.Span)),
"handle.user.created",
)
defer span.End()
}
逻辑分析:trace.ContextWithSpan 将已存在的 Span 显式挂载到新 ctx,确保事件处理链路不丢失父 Span ID 与 tracestate。
graph TD A[HTTP Request] –> B[Gin Middleware] B –> C[Gin Handler] C –> D[Domain Event Emitted] D –> E[Async Event Handler] B & E –> F[Shared TraceID & SpanID]
4.4 分布式事务(Saga/TCC)场景下Span跨阶段延续与异常分支链路补全
在 Saga/TCC 模式中,各服务节点需共享同一 TraceID 并传递 SpanContext,确保补偿/确认操作可被精准归因。
数据同步机制
Saga 各子事务通过 Baggage 注入 saga_id 和 compensable_id,实现跨服务链路锚定:
// 在发起端注入上下文
Tracer.currentSpan()
.setTag("saga_id", "saga-7f3a9b")
.setTag("stage", "order-create");
此处
saga_id作为全局事务标识符贯穿所有正向与补偿阶段;stage标签区分执行阶段(如payment-charge/payment-compensate),为异常后链路补全提供语义依据。
异常分支链路补全策略
| 阶段类型 | 是否生成新 Span | 是否继承父 SpanID | 补全关键标签 |
|---|---|---|---|
| 正向执行 | 否(复用) | 是 | stage, saga_id |
| 补偿操作 | 是(独立 Span) | 否(但 link_to 父 Span) | compensated_for, error_stage |
graph TD
A[order-create] -->|success| B[payment-charge]
B -->|fail| C[payment-compensate]
C -.->|link_to| B
补偿 Span 通过 Link 关联失败阶段,使 Jaeger/Grafana 中可一键跳转至根因 Span。
第五章:生产环境链路追踪稳定性保障与效能评估体系
核心稳定性保障机制设计
在某金融级支付平台的生产环境中,我们通过三重熔断策略保障链路追踪系统自身不成为故障源:① SDK 端采样率动态降级(当 JVM GC Pause > 200ms 连续触发5次,自动将采样率从10%降至0.1%);② Agent 与后端 Collector 间启用 gRPC Keepalive + 自适应重连(初始间隔500ms,指数退避至30s上限);③ 后端存储层部署双写分流:OpenTelemetry traces 写入 ClickHouse(主查)与 Kafka(灾备回放),写失败时自动切换通道并触发企业微信告警。该机制上线后,追踪系统全年因自身异常导致的 tracing 数据丢失率从 3.7% 降至 0.02%。
效能评估指标体系构建
我们定义了四级可观测性效能指标,全部通过 Prometheus + Grafana 实时采集:
| 指标类别 | 关键指标 | 告警阈值 | 数据来源 |
|---|---|---|---|
| 接入覆盖度 | 服务实例 trace 注入率 | Agent heartbeat 日志解析 | |
| 链路完整性 | 跨服务 span 上下文丢失率 | > 0.8% | Jaeger UI 查询 API 统计 |
| 性能开销 | 单请求平均 trace 处理耗时 | > 1.2ms | JVM Agent ByteBuddy Hook 计时 |
| 存储健康度 | ClickHouse traces 表写入延迟 P99 | > 800ms | ClickHouse system.metrics |
真实故障复盘案例
2024年Q2,某核心订单服务出现偶发性 5xx 错误,传统日志排查耗时 4 小时。启用链路追踪效能评估看板后,发现其 trace_id 在网关层生成后,下游 3 个服务中仅有 2 个上报 span,缺失服务的 otel.sdk.disabled 环境变量被意外置为 true。通过自动化巡检脚本(每5分钟扫描所有 Pod 的 env)定位到配置漂移,12 分钟内完成批量修复。该案例验证了“接入覆盖度”指标对配置治理的实际价值。
自动化巡检与自愈流水线
基于 Argo Workflows 构建每日凌晨 2:00 执行的稳定性巡检任务:
- 调用 OpenTelemetry Collector 的
/metrics接口抓取otelcol_processor_batch_batch_size_sum; - 对比前7日均值,若波动超 ±35%,触发 Slack 通知并自动执行
kubectl rollout restart deployment/otel-collector; - 同步拉取 Kafka 中最近1小时 trace 数据,用 Spark SQL 计算
span.kind=CLIENT但无对应span.kind=SERVER的异常链路比例,>0.5% 则启动服务网格侧 Envoy access log 关联分析。
flowchart LR
A[Prometheus Alert] --> B{Is High Latency?}
B -->|Yes| C[Auto-trigger Trace Sampling Analysis]
C --> D[Compare with Baseline Profile]
D --> E[If Drift > 15% → Rollback Last Release]
B -->|No| F[Check Span Context Propagation]
容量规划模型验证
采用真实流量压测+合成数据混合建模:使用 k6 以 120% 生产峰值 QPS 持续压测 30 分钟,同时注入 5% 高熵 trace(含 20+ 层嵌套、每个 span 附加 15 个 attribute)。结果显示,当单 Collector 实例 CPU 使用率突破 78% 时,otelcol_exporter_queue_length 指标突增至 12.4k,触发水平扩容逻辑——该阈值经 6 轮压测校准后固化为 SLO。
