4、调用链超时模型:客户端超时、服务端超时、网络延迟、排队延迟,如何用Span属性标记
在分布式追踪中,调用链超时并非单一维度的指标,而是由多个独立环节叠加而成的复合延迟模型。只有将超时按来源拆解为客户端超时、服务端超时、网络延迟和排队延迟,并通过 Span 属性精确标记,才能建立有效的误差预算台账。
4.1 超时模型四象限
从调用链的端到端视角,一次请求的总耗时可以分解为四个基本象限。下表列出了每个象限的物理含义、典型场景以及对应的 OpenTelemetry Span 属性标记方案。
| 象限 | 定义 | 典型场景 | Span 属性标记 |
|---|---|---|---|
| 客户端超时 | 调用方发起请求后,等待响应所允许的最大时间 | HTTP 客户端设置 timeout=5s,超时后主动取消 | http.client.timeoutrpc.client.deadline |
| 服务端超时 | 服务端处理请求所允许的最大时间 | gRPC 服务端 deadline=10s,超时返回 DEADLINE_EXCEEDED | http.server.timeoutrpc.server.deadline |
| 网络延迟 | 数据在网络上传输所消耗的时间 | 跨 AZ 调用,RTT 波动 2ms~50ms | net.host.connection_timenet.peer.latency |
| 排队延迟 | 请求在服务端队列中等待被处理的时间 | 线程池满、消息队列积压、连接池等待 | queue.durationthread.pool.wait_time |
关键原则: 客户端超时和服务端超时是契约性的,由配置或代码显式设定;网络延迟和排队延迟是观测性的,需要通过 Span 属性从实际耗时中剥离计算。
4.2 用 Span 属性标记四类超时
OpenTelemetry 的 Span 属性(Attributes)是携带键值对的元数据容器。针对超时模型,我们推荐以下标准化属性命名方案,确保在误差预算台账中可聚合、可过滤。
4.2.1 客户端超时属性
// HTTP 客户端超时
span.setAttribute("http.client.timeout", 5000); // 单位 ms
span.setAttribute("http.client.timeout_exceeded", true);
// gRPC 客户端截止时间
span.setAttribute("rpc.client.deadline", "2025-01-15T10:30:00Z");
span.setAttribute("rpc.client.deadline_remaining_ms", 1200);
4.2.2 服务端超时属性
// HTTP 服务端超时
span.setAttribute("http.server.timeout", 10000); // 单位 ms
span.setAttribute("http.server.timeout_reason", "idle_timeout");
// gRPC 服务端截止时间
span.setAttribute("rpc.server.deadline", "2025-01-15T10:30:05Z");
span.setAttribute("rpc.server.deadline_exceeded", true);
4.2.3 网络延迟属性
// TCP 连接建立耗时
span.setAttribute("net.host.connection_time_ms", 3);
// 对端往返延迟估算
span.setAttribute("net.peer.latency_ms", 12);
// DNS 解析耗时
span.setAttribute("net.dns.lookup_time_ms", 1);
4.2.4 排队延迟属性
// 线程池等待时间
span.setAttribute("thread.pool.wait_time_ms", 45);
span.setAttribute("thread.pool.name", "biz-worker");
// 消息队列排队时间
span.setAttribute("queue.duration_ms", 230);
span.setAttribute("queue.name", "order.create.queue");
span.setAttribute("queue.position", 7);
4.3 超时模型分解流程图
下图展示了一次完整 RPC 调用中,四个超时象限在时间轴上的叠加关系,以及 Span 属性在哪个阶段被标记。
4.4 属性标记最佳实践
- 统一命名空间: 使用
http.*、rpc.*、net.*、queue.*前缀,避免团队间属性名冲突。 - 单位标准化: 所有时间类属性统一使用毫秒(ms),并在属性名中显式标注
_ms后缀。 - 布尔标记: 超时是否发生使用
_exceeded或_timeout布尔属性,便于在误差预算中直接过滤。 - 保留原始值: 客户端和服务端的超时配置值(如 5000ms)必须记录,用于计算超时裕量。
- 避免重复计算: 网络延迟和排队延迟应从 Span 的
start_time和end_time中通过子 Span 或事件时间戳推导,不要手动赋值。
常见陷阱: 不要将客户端超时和服务端超时混用同一个属性名。例如,
http.timeout 无法区分是调用方超时还是被调用方超时,导致误差预算台账无法定位故障方向。必须严格使用 http.client.timeout 和 http.server.timeout。
4.5 超时模型在误差预算中的聚合方式
当 Span 属性被正确标记后,误差预算台账可以按以下维度进行聚合计算:
| 聚合维度 | 属性键 | 计算公式 | 预算消耗条件 |
|---|---|---|---|
| 客户端超时率 | http.client.timeout_exceeded |
count(exceeded=true) / total | 超过预算阈值(如 0.1%) |
| 服务端超时率 | rpc.server.deadline_exceeded |
count(exceeded=true) / total | 超过预算阈值(如 0.05%) |
| 网络延迟 P99 | net.peer.latency_ms |
percentile(0.99) | P99 > 50ms |
| 排队延迟 P95 | queue.duration_ms |
percentile(0.95) | P95 > 200ms |
通过上述模型,SRE 团队可以在误差预算仪表盘中一眼识别出超时的根因象限:是客户端配置过紧、服务端处理过慢、网络链路抖动,还是服务端排队积压。每个象限都有对应的 Span 属性作为证据,无需再依赖人工日志排查。