文章目录
- 环境与现象
- 验证命令
- 20 次测试结果
- 为什么 HTTP/2 的 starttransfer 会更早
- 用另一个 streaming 接口做交叉验证
- 如何无损新增 HTTPS/HTTP2
- 验证 HTTPS/HTTP2 是否生效
- 快速参考
1. 环境与现象
这次排查的是一个 streaming 接口的入口差异。公网入口走 HTTPS 443 + HTTP/2,VPC 内网入口走 HTTP 80 + HTTP/1.1。业务请求都是 stream=true,后端生成内容的总耗时接近,但 curl 看到的 time_starttransfer 差很多。

环境如下:
| 项目 | 公网入口 | VPC 内网入口 |
|---|---|---|
| 协议 | HTTPS | HTTP |
| 端口 | 443 | 80 |
| HTTP 版本 | HTTP/2 | HTTP/1.1 |
| ALB Gzip | 开启 | 开启 |
| ALB IdleTimeout | 60s | 60s |
| ALB RequestTimeout | 300s | 300s |
| 请求类型 | streaming | streaming |
这类现象容易被误判为“内网比公网慢 2 秒”。实际要先拆开两个指标:
time_starttransfer:curl 收到第一个字节的时间。time_total:整个请求结束的总耗时。
对 streaming 接口来说,time_starttransfer 不一定等于“模型开始吐第一个有效 content chunk 的时间”。
2. 验证命令
验证时响应体全部丢弃,只看状态码、HTTP 版本和 timing:
curl -sS -o /dev/null \--connect-timeout 5 \--max-time 20 \-w 'code=%{http_code} version=%{http_version} namelookup=%{time_namelookup} connect=%{time_connect} appconnect=%{time_appconnect} starttransfer=%{time_starttransfer} total=%{time_total} remote=%{remote_ip} err=%{errormsg}\n' \-H "Content-Type: application/json" \-H "Authorization: Bearer <TOKEN>" \-d '{"model":"<MODEL>","messages":[{"role":"user","content":"ping"}],"stream":true}' \"https://<PUBLIC_HOST>/v1/chat/completions"
VPC 内网入口同样测试:
curl -sS -o /dev/null \--connect-timeout 5 \--max-time 20 \-w 'code=%{http_code} version=%{http_version} namelookup=%{time_namelookup} connect=%{time_connect} appconnect=%{time_appconnect} starttransfer=%{time_starttransfer} total=%{time_total} remote=%{remote_ip} err=%{errormsg}\n' \-H "Content-Type: application/json" \-H "Authorization: Bearer <TOKEN>" \-d '{"model":"<MODEL>","messages":[{"role":"user","content":"ping"}],"stream":true}' \"http://<VPC_HOST>/v1/chat/completions"
注意两点:
- 不要把 token 打到终端或日志里。
- streaming 接口要同时看
version、starttransfer和total,不要只看一个字段。
3. 20 次测试结果
从源 ECS 跑 20 次,结果如下。
公网入口:
20/20 成功,HTTP 200
starttransfer: 0.047s - 0.064s
total 平均值: 约 1.99s
VPC 内网入口:
20/20 成功,HTTP 200
starttransfer: 1.37s - 2.23s
total 平均值: 约 1.89s
结论很直接:
- VPC 内网入口总耗时没有比公网慢,基本持平。
- 差异主要集中在
time_starttransfer。 - 公网
HTTPS + HTTP/2的starttransfer约 0.05s。 - VPC
HTTP + HTTP/1.1的starttransfer约 1.3s - 2.4s。
4. 为什么 HTTP/2 的 starttransfer 会更早
先区分三个概念:
| 写法 | TLS | HTTP 版本 | 说明 |
|---|---|---|---|
| HTTP/1.1 | 无 TLS | HTTP/1.1 | 明文 HTTP,常见 80 端口 |
| HTTPS/1.1 | 有 TLS | HTTP/1.1 | 加密了,但应用层还是 HTTP/1.1 |
| HTTPS/2 | 有 TLS | HTTP/2 | TLS 握手里通过 ALPN 协商 HTTP/2 |
HTTPS 不等于 HTTP/2。HTTPS 只是 TLS 加密;HTTP/2 是应用层协议版本。一般浏览器和 curl 访问 HTTPS 时会通过 ALPN 协商,如果服务端支持,才会走 HTTP/2。
对普通非流式接口来说,time_starttransfer 通常接近后端开始返回响应的时间。对 streaming 接口来说,情况更复杂:

- HTTP/2 可以更早返回响应头或 HTTP/2 frame。
- curl 收到第一个响应字节后,就会记录
time_starttransfer。 - 这个“第一个字节”可能还不是业务上第一个有效 content chunk。
- HTTP/1.1 下常见表现是更接近首个有效 chunk 到达时才记录首字节。
所以这次看到的现象不是“内网链路慢 2 秒”,而是两套入口的协议行为不同。
更准确的业务体感指标应该是:
- 总耗时
time_total - 首个有效 content chunk 时间
- 端到端应用日志里的首 token / 首 chunk 时间
5. 用另一个 streaming 接口做交叉验证
为了确认这不是单个服务偶然现象,又用另一个 streaming 接口做了验证。该接口的内网入口原本是 HTTP/1.1,后来给同一个内网 ALB 新增了 HTTPS 443 + HTTP/2。
新增后同一域名同时支持:
http://<INTERNAL_HOST>/... -> 80 HTTP/1.1
https://<INTERNAL_HOST>/... -> 443 HTTPS + HTTP/2
验证结果:
HTTP 80:
code=200
version=1.1
starttransfer=1.71s - 2.51sHTTPS 443:
code=200
version=2
starttransfer=0.053s - 0.054s
这说明在同一个内网入口上,新增 HTTPS/HTTP2 后,time_starttransfer 确实会从秒级降到几十毫秒级。
6. 如何无损新增 HTTPS/HTTP2
最小影响方案是只新增 443,不动 80:
80 HTTP/1.1 保留,兼容旧调用
443 HTTPS/2 新增,给需要 streaming 体感一致的客户端使用
如果 ALB 是手工管理,新增 HTTPS listener 时配置:
协议: HTTPS
端口: 443
HTTP/2: 开启
Gzip: 保持开启
证书: 覆盖内网域名
转发动作: 指向与 80 Host 规则相同的 ServerGroup
如果 ALB 是 ACK ALB Ingress Controller 管理,不建议直接在 ALB 控制台改。应改 AlbConfig 和 Ingress。
Ingress 上常见写法:
metadata:annotations:alb.ingress.kubernetes.io/listen-ports: '[{"HTTP":80},{"HTTPS":443}]'
TLS host:
spec:tls:- hosts:- <VPC_HOST>
原则:
- 不删除 80。
- 不配置 HTTP 到 HTTPS 强制跳转。
- 不改 DNS。
- 不改后端 Service。
- 443 先和 80 指向同一个后端,再做验证。
7. 验证 HTTPS/HTTP2 是否生效
新增 443 后,从源 ECS 测:
curl --http2 -sS -o /dev/null \-w 'code=%{http_code} version=%{http_version} starttransfer=%{time_starttransfer} total=%{time_total}\n' \-H "Content-Type: application/json" \-H "Authorization: Bearer <TOKEN>" \-d '{"model":"<MODEL>","messages":[{"role":"user","content":"ping"}],"stream":true}' \"https://<VPC_HOST>/v1/chat/completions"
预期:
code=200
version=2
starttransfer 接近 0.05s - 0.1s
total 不明显变差
再回归 HTTP:
curl -sS -o /dev/null \-w 'code=%{http_code} version=%{http_version} starttransfer=%{time_starttransfer} total=%{time_total}\n' \-H "Content-Type: application/json" \-H "Authorization: Bearer <TOKEN>" \-d '{"model":"<MODEL>","messages":[{"role":"user","content":"ping"}],"stream":true}' \"http://<VPC_HOST>/v1/chat/completions"
预期:
HTTP 仍可用
version=1.1
8. 快速参考
判断一个 streaming 入口是不是 HTTP/2:
curl --http2 -sS -o /dev/null \-w 'code=%{http_code} version=%{http_version} starttransfer=%{time_starttransfer} total=%{time_total}\n' \"https://<HOST>/<PATH>"
如果 version=2,说明 HTTP/2 生效。
排查顺序:
1. 看 DNS 指向哪个 ALB
2. 看 listener: 80 HTTP 还是 443 HTTPS
3. 看 HTTPS listener 是否 Http2Enabled=true
4. 看 Host 转发规则,不要只看默认转发
5. 确认 80 和 443 是否转发到同一个 ServerGroup
6. 跑 20 次 curl,比较 version/starttransfer/total
几个容易踩的点:
- HTTPS 不等于 HTTP/2。
time_starttransfer不等于首个有效 content chunk。- ALB listener 默认转发不一定是实际命中的转发,Host 规则会覆盖默认转发。
- 新增 443 时先保留 80,避免影响旧客户端。
- ACK 管理的 ALB 要改 AlbConfig/Ingress,不要只在 ALB 控制台手工改。