Nginx 反代 SSE 流式接口:gzip 压缩与 proxy_buffering 的冲突与解法
问题背景
把大模型 API 的中转面板部署在新加坡的机器上,国内打开一个 1MB 的单页应用,晚高峰经常卡到 40 秒以上,甚至直接超时。服务端日志看不出问题,curl 本机测又是 0.05 秒返回,很容易误判成"服务挂了"。
真正的原因有三个叠加:页面单文件接近 1MB、Nginx 没有开 gzip、跨境链路 RTT 高。开了 gzip 之后首屏问题解决了,但新的坑马上出现——同一个 Nginx 里的 SSE 流式接口开始"只返回第一个字就停住"。
为什么 gzip 会卡死 SSE
SSE(Server-Sent Events)依赖 text/event-stream 长连接,数据是一小块一小块推给客户端的。而 gzip 压缩需要攒够缓冲区才会输出,gzip_proxied 与 proxy_buffering 同时开启时,Nginx 会把上游的碎片响应先缓冲再压缩,客户端感知到的就是"卡住不动"。
结论很直接:压缩和流式不能放在同一个 location 里。静态页面要缓冲 + 压缩,SSE 接口要关闭缓冲、关闭压缩。
配置拆成两个 location
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml;
# 首页:整页缓冲 + 压缩
location = / {
proxy_pass http://127.0.0.1:8789;
proxy_buffering on;
proxy_buffers 32 64k;
proxy_buffer_size 128k;
proxy_busy_buffers_size 256k;
proxy_set_header Accept-Encoding "gzip";
}
# API:保留流式,不缓冲
location /v1/ {
proxy_pass http://127.0.0.1:8789;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 600s;
proxy_set_header Connection "";
chunked_transfer_encoding on;
}
实测首页从 1,007,540 字节压到 298,643 字节,约 1/3.4,国内首屏时间从 40 秒降到可接受范围;/v1/chat/completions 的 stream:true 首字节仍然正常返回,SSE 没有受影响。
几个容易踩的细节
gzip_comp_level不要盲目上 9,对 1MB 的文本 9 和 6 只差几 KB,但 CPU 占用翻倍;机器核数少时优先 6。- 只有
gzip_types里列出的类型才会压缩,text/html默认就压,别重复写。 - 上游如果自己已经返回
Content-Encoding: gzip,Nginx 默认不会再压一遍,注意别双重压缩。 proxy_read_timeout对 SSE 必须放大,默认 60 秒会把长回答直接掐断。- 改完一定先
nginx -t再 reload,配置语法错误会让整个站点 502。
如何验证
不要只在本机 curl。本机到服务端是内网,永远快。要在真实客户端侧测:
curl -o /dev/null -H 'Accept-Encoding: gzip' \
-w 'http=%{http_code} connect=%{time_connect}s ttfb=%{time_starttransfer}s size=%{size_download}\n' \
https://example.com/
对比 size_download 是否明显变小、time_starttransfer 是否下降;SSE 则用 curl -N 看数据是否持续输出而不是攒一大块。
小结
跨境部署的面板类服务,性能问题往往不在代码而在传输层:先量体积,再开压缩,最后按路径区分缓冲策略。压缩与流式分开配置,是这套组合里性价比最高的一步。
- 点赞
- 收藏
- 关注作者
评论(0)