概述
Node 的 fetch 默认没有请求超时。依赖方接受连接后一直不响应,请求会一直挂到套接字层放弃,可能是几分钟。这段时间里 worker 被占用、数据库连接被借出,客户端多半已经重试。一个慢依赖就此演变为自我造成的故障,因为没有任何机制限制等待时间。
步骤
- 每个出站请求都传入基于依赖延迟预算的显式超时:
const res = await fetch(url, { signal: AbortSignal.timeout(2000) });
- 把
TimeoutError/AbortError映射为 504,并对非幂等操作标记为不可重试。 - 用 Toxiproxy 注入确定性的延迟:
toxiproxy-cli create payment -l 127.0.0.1:8666 -u payment-provider:443
toxiproxy-cli toxic add payment -t latency -a latency=5000
- 断言响应时间小于 3 秒,并检查
pool.totalCount === pool.idleCount,确认没有连接被占用。 - 移除 toxic,确认下一次请求无需重启即可恢复。
验证
注入 5 秒延迟后,结账接口应在约 2 秒返回 504,响应体包含 retryable: false。请求结束后连接池应全部空闲;移除延迟后同一接口立即返回 200。重复执行多次结果一致,说明测试不依赖真实网络抖动。
注意事项
- 客户端超时不会取消上游已经接受的业务处理,超时属于“结果未知”而非失败。
- 网络层超时与应用层超时是两层,需保持一致,避免代理比客户端等待更久。
- 对支付等非幂等操作,超时后盲目重试会造成重复扣款,应配合幂等键核对。
- Toxiproxy 自身增加了一跳网络,需要在真实环境中确认延迟数值。
- 超时后若连接或定时器未释放,下一次故障会表现为连接池耗尽而不是响应变慢。