这类问题我又碰上了一次。批处理脚本对同一台推理服务发起 500 次请求,单次耗时 410ms,其中 TLS 握手就有 120ms,脚本里每次都用 requests.post 顶层函数调用,没有定义 Session。

第一反应是「服务端开了 keep-alive,但客户端先发 Connection: close」,验证下来不成立。

排查方向其实有限,先把可能性排一排。顶层函数每次创建新 Session,连接用完即关;没有配置 HTTPAdapter 的 pool_size,默认只有 10 个连接;重定向与重试参数散落在每个调用上,无法统一复用连接。

上来先改成全局 Session 并设置连接池大小

python -c "import requests;s=requests.Session();a=requests.adapters.HTTPAdapter(pool_connections=50,pool_maxsize=50);s.mount('https://',a);print(s)"

然后用 curl 计时拆出握手与首字节各自耗时

curl -s -o /dev/null -w 'dns=%{time_namelookup} tls=%{time_appconnect} ttfb=%{time_starttransfer}\n' https://api.example.com/v1/models

再往下确认复用后第二次请求的握手时间为 0

curl -s -o /dev/null -o /dev/null -w '%{time_appconnect}\n' https://api.example.com/v1/models https://api.example.com/v1/models

批量任务里对比单次平均耗时

python batch_call.py --total 500 --session reuse --report

改完先别撤,确认这几项。500 次请求里只有第一次出现 TLS 握手,后续 time_appconnect 接近 0,平均单次耗时从 410ms 降到 290ms 左右。

容易忽略的是,连接复用对推理服务收益最大的是短请求,长生成请求本来就占满连接,别用平均耗时掩盖连接池不足的问题。线上能稳定复现的问题都不算难,难的是只在大流量下才出现的那种。

作者 9823189