Go AI 后端超时树:每个下游都要有自己的时间预算
Go AI 后端超时树:每个下游都要有自己的时间预算
AI 后端链路经常很长:认证、读取配置、检索知识库、重排、调用模型、写日志、保存结果、推送通知。很多服务只在最外层设置一个总超时,内部下游随意等待。结果某个慢依赖吃掉全部时间,模型还没开始就已经接近超时。Go 服务里,超时应该像一棵树,每个下游都有自己的预算。
超时树的目标,是让请求在有限时间内做出可解释的取舍。
一、先定义总预算
flowchart TD
A[Request 8s] --> B[Auth 300ms]
A --> C[Retrieval 1500ms]
A --> D[Rerank 800ms]
A --> E[LLM 5000ms]
A --> F[Persist 300ms]
用户请求如果承诺 8 秒内返回,就要把 8 秒拆给各个阶段。认证不能等 2 秒,检索不能无限重试,持久化也不能卡住主流程。预算拆清后,服务才能在某个下游变慢时及时降级。
预算不是平均分。模型调用可能需要最多时间,认证和配置读取应该很短,日志写入可以异步。每个阶段的预算要根据业务价值和依赖特性分配。
二、Context 要逐层派生
ctx, cancel := context.WithTimeout(parent, 1500*time.Millisecond)
defer cancel()
docs, err := retriever.Search(ctx, query)
Go 的 context 很适合传递取消和超时,但要避免所有下游共用一个没有边界的 ctx。每个阶段可以从父 ctx 派生子 ctx,设置自己的 timeout。父请求取消时,所有子任务都应该停止。
同时要避免忘记 cancel。即使请求提前返回,也要释放 timer 和相关资源。长链路服务里,这些小疏漏会慢慢变成 goroutine 泄漏和资源浪费。
三、降级策略要跟预算绑定
fallback:
retrieval_timeout: use_cached_docs
rerank_timeout: skip_rerank
persist_timeout: async_retry
超时不是只有失败一种结果。检索超时可以使用缓存,重排超时可以跳过,日志写入超时可以异步补偿,模型超时可以返回部分结果或提示稍后查看。每个下游都要提前定义超时后的动作。
降级也要可见。响应里可以带上内部标记,日志里记录哪个阶段降级。否则用户觉得回答质量下降,团队却不知道这次请求跳过了重排或用了旧缓存。
四、观测要按阶段打点
{
"trace_id": "tr_001",
"retrieval_ms": 420,
"rerank_ms": 180,
"llm_ms": 4630,
"timeout_stage": null
}
只有总耗时,看不出问题。每个阶段的耗时、超时、重试和降级都要进入 trace 或结构化日志。这样当 TP99 升高时,能知道是检索慢、模型慢,还是持久化卡住。
阶段打点还可以反过来调整预算。某个阶段长期只用 50ms,却给了 1 秒预算;另一个阶段经常超时,却对结果很关键,就需要优化或扩容。预算应该跟着数据演进。
还要把客户端取消传到底。用户关闭连接后,检索、模型调用和持久化不应该继续无意义消耗资源。超时树和取消树最好一起设计,让请求退出时下游能及时收手。
五、总结
Go AI 后端要把请求总超时拆成超时树,为每个下游分配独立时间预算,并用 context 逐层派生、绑定降级策略和阶段观测。
超时不是最后一刻才发现来不及,而是从请求进入系统开始,就清楚每一步最多能花多久。
更多推荐



所有评论(0)