应通过端到端延迟可观测体系评估异步报错影响:一、在协程入口打点,分别统计正常与错误路径耗时;二、按p95阈值过滤伪错误延迟;三、用“错误请求耗时−同类成功p50”计算真实惩罚占比。

直接用 try-catch 统计异步报错耗时占比,本质上是个误导性操作——catch 块本身不耗时,真正耗时的是错误发生前的 I/O 等待、序列化、网络重试或异常构造过程。想定量评估“异步报错对整体吞吐的影响”,关键不是抓 catch,而是把错误路径纳入端到端延迟可观测体系。
下面分三步说清怎么做:
一、在协程入口打点,区分正常流与错误流
不要等异常抛到顶层才统计。应在每个异步调用起点(如 await fetch_xxx() 前)启动计时,并在 try 和 except 分支中分别记录完成时间:
import time
from collections import defaultdict
error_stats = defaultdict(list) # {error_type: [latency_ms, ...]}
async def safe_fetch_user(user_id):
start = time.perf_counter()
try:
data = await httpx.AsyncClient().get(f"/user/{user_id}")
latency = (time.perf_counter() - start) * 1000
# 记录成功路径:可选,用于对比基线
return data.json()
except httpx.TimeoutException as e:
latency = (time.perf_counter() - start) * 1000
error_stats["Timeout"].append(latency)
except httpx.HTTPStatusError as e:
latency = (time.perf_counter() - start) * 1000
error_stats["HTTPError"].append(latency)
except Exception as e:
latency = (time.perf_counter() - start) * 1000
error_stats[type(e).__name__].append(latency)
⚠️ 注意:
time.perf_counter()在协程中安全,但别用datetime.now()或time.time()(受系统时钟跳变影响)。
二、聚合时剔除“伪错误延迟”,只算有效惩罚
很多错误实际是快速失败(如 DNS 解析失败
- 先计算所有成功请求的 P95 延迟(例如 82ms)
- 再筛选出 错误请求中延迟 > 1.5×P95 的样本(即 > 123ms)
- 这部分才代表“真正拖慢流水线的错误开销”
base_p95 = get_success_p95_latency() # 从监控系统或滑动窗口计算
costly_errors = [
lat for lat in error_stats["Timeout"]
if lat > 1.5 * base_p95
]
三、计算真实耗时占比:用“错误导致的额外等待”替代“catch执行时间”
最终指标不是 sum(catch_time) / total_time(几乎为 0),而是:
错误惩罚占比 = Σ(错误请求耗时 − 对应成功请求预期耗时) / 总处理时间
其中“对应成功请求预期耗时”可用同类型请求的历史 P50 延迟代替。例如:
| 请求类型 | 错误数 | 平均错误耗时 | 同类成功 P50 | 单次惩罚(ms) | 总惩罚(ms) |
|---|---|---|---|---|---|
| Redis 查询 | 142 | 217ms | 12ms | 205 | 29,110 |
| HTTP 调用 | 89 | 1630ms | 98ms | 1532 | 136,348 |
若总处理时间为 200 秒(200,000ms),则错误导致的净延迟占比 ≈ (29,110 + 136,348) / 200,000 ≈ 82.7% —— 这才是真正影响吞吐的代价。
不复杂但容易忽略











