EKS 上 FastAPI 用 StreamingResponse 输出 SSE,前面挂 AWS ALB,服务端每 15 秒发一行心跳,超过 60 秒的回答仍被断开,浏览器报 net::ERR_INCOMPLETE_CHUNKED_ENCODING,本地直连 8000 端口正常。
一开始怀疑是「gzip 中间件把流式内容攒成整包」,查了一圈,方向不对。
排查方向其实有限,先把可能性排一排。ALB 的 idle_timeout 默认 60 秒,到点直接断连接。还有一种是后端没有逐块 flush,数据停在 uvicorn 缓冲区。
还有一种情况,target group 的健康检查与长连接抢端口。
第一件事是确认负载均衡器的 idle_timeout 现值
aws elbv2 describe-load-balancer-attributes --load-balancer-arn $ALB_ARN --output json
接着把空闲超时调到 300 秒并等待生效
aws elbv2 modify-load-balancer-attributes --load-balancer-arn $ALB_ARN --attributes Key=idle_timeout.timeout_seconds,Value=300
服务端固定每 15 秒发一行注释心跳
python -c "print(': keep-alive\n\n', flush=True)"
末了关掉流式路由上的 gzip 压缩后复测
curl -N -H 'Accept-Encoding: identity' https://api.example.com/chat/stream -H "Authorization: Bearer $TOKEN"
恢复的判定其实很直接。用 curl 观察 5 分钟的长回答,连接不断开,末尾能收到 DONE,浏览器控制台不再出现 INCOMPLETE_CHUNKED_ENCODING。
云负载均衡的空闲超时和应用的读超时是两层,任何一层到点都会断,心跳间隔要小于两者中较小的那个,改动后记得等几十秒让配置真正下发。改动前把原配置备份一份,出问题能马上回滚,这比任何技巧都实用。
