目标
挂起的服务很少烧资源:它把请求堆积在一个永远读不完的读操作上。证据不在 CPU 图里,而在套接字表里。统计指向单一依赖的 established 套接字数、再快照进行中请求的等待点,就能点名元凶。永久解法是给每个外部调用加截止时间,最好是从调用方预算推导并沿链路传播的同一份截止时间。
步骤要点
- 用 ss 统计目标依赖 443 端口的 established 套接字:CPU 空闲却有数百条连接,即是“读无期限”的典型signature。
- 向进程发诊断信号导出在途请求快照,确认全部停在等待 payments 响应的阶段。
- 用黑洞地址(10.255.255.1 只建连不应答)在预发确定性复现挂起,证明现状没有超时。
- 给调用加 AbortSignal.timeout(3000),复现场景改为三秒后抛 TimeoutError 中断。
- 把下游调用的 signal 换成从请求剩余预算推导的 ctx.deadline,让重试消耗同一预算而非叠加超时。
排错提示
加了超时池子仍被耗尽,是客户端对超时结果无上限重试,需按总耗时或次数封顶;超时触发但套接字残留说明 abort 未销毁连接,要确认客户端在 abort 时销毁 socket 并给连接池设空闲上限;只在生产出现就说明本地缺真实慢依赖,用黑洞地址在 CI 里断言截止时间行为。
适用边界
截止时间只是把等待换成错误,用户仍然失败,除非存在降级路径;单调用超时必要但不充分,不做预算传播时各阶段之和仍可能超出任何单一预算。
代码示例
# Requests parked on one dependency
ss -tn state established "( dport = :443 )" | grep -c <payment-ip>
# Reproduce a hang with a black-hole address (never responds)
PAYMENT_BASE_URL=http://10.255.255.1:9000 npm run reproduce:hang