更新日期:2026-10-18
AI 应用在本机调用时逐段输出,经过 Nginx 后却等到最后一次性出现;或者开始回答正常,停顿一段时间后连接被切断。这类问题要区分响应缓冲、上游静默、客户端读取方式和整条链路的超时。
本文面向已经有应用服务的 Nginx HTTP 反向代理。示例上游为同机 127.0.0.1:8000,/api/stream 表示流式接口,/ws/ 表示 WebSocket 路径;实际部署必须改成应用真实路径。示例使用仅回环监听的测试入口,不包含公网 TLS、登录或 API 密钥配置。
SSE 与 WebSocket 分别需要什么
SSE 基于 HTTP 响应持续传输事件,常见响应类型为 text/event-stream;WebSocket 则需要完成协议升级,之后在长连接中双向交换消息。
| 类型 | 重点 | 常见误区 |
|---|---|---|
| SSE 或其他 HTTP 流 | 应用及时 flush、代理不积攒响应、客户端持续读取 | 给它添加 WebSocket Upgrade 头 |
| WebSocket | HTTP 升级头、上游协议与空闲连接维护 | 只关闭响应缓冲就认为配置完成 |
| 普通 JSON API | 正常缓冲与合理超时 | 为所有请求全局禁用缓冲 |
先确认应用使用哪种方式,再修改对应路由。一个聊天界面可能只使用 HTTP 流,并没有 WebSocket 服务。
一份用于本机验证的配置
下面整体放在 Nginx 的 http 上下文中,例如由主配置在 http 内包含的测试文件。map 不能放进 server 或 location。先确认 18081 未占用。
map $http_upgrade $mf8_connection_upgrade {
default upgrade;
'' close;
}
server {
listen 127.0.0.1:18081;
server_name localhost;
location = /api/stream {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
gzip off;
proxy_connect_timeout 5s;
proxy_read_timeout 300s;
proxy_send_timeout 60s;
}
location /ws/ {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $mf8_connection_upgrade;
proxy_read_timeout 300s;
}
}
这里 proxy_pass 没有附加 URI,保留请求路径传给上游。若应用实际监听另一条路径,需要明确设计映射,不能随意在地址末尾添加斜杠后期待行为不变。
数字是测试起点,不是所有模型的最佳参数。上线前应依据应用等待时间、网关约束和资源预算调整;公网入口继续沿用已验证的 HTTPS 与访问控制配置,不把这个本机示例直接开放到所有网卡。
缓冲开关解决哪一层
proxy_buffering off 控制 Nginx 的上游响应缓冲;proxy_request_buffering 控制请求体缓冲,是另一件事。普通文本生成请求不必因为响应是流式,就一并关闭请求体缓冲。
应用也可以通过响应头表达缓冲要求,但应确认当前 Nginx 配置没有忽略它。上游若自身压缩、缓存或攒到结束才 flush,单独修改 Nginx 无法产生实时片段。Nginx proxy 模块
浏览器端也要用流式读取方式。若前端一直等待完整的 response.json(),服务端及时发送片段也不会自动变成逐字显示。验证应覆盖应用、代理与客户端三层。
超时不是一次请求的总时长
proxy_read_timeout 约束上游两次读取之间的等待,并不简单等于整个回答允许持续的总时间。一个持续发送片段的响应可以运行很久,而长时间无任何数据的响应可能触发空闲等待限制。
应用应根据自己的协议发送合适心跳,WebSocket 可以使用协议级 ping,SSE 可以使用有效事件流中的注释等方式。心跳只说明连接仍活着,不能掩盖模型任务永久卡死;业务仍要设置总任务期限和取消机制。
还需检查 CDN、负载均衡器、应用服务器和模型调用客户端。Nginx 调大到 300 秒,不会自动改变其他层的限制。WebSocket 代理说明
先直连,再经过代理
在测试环境中,如果 /api/stream 是无需认证的 GET 演示接口,可以对比:
curl -N --max-time 20 http://127.0.0.1:8000/api/stream
curl -N --max-time 20 http://127.0.0.1:18081/api/stream
真实接口若需要 POST、请求体或认证,应使用应用规定的请求,不把密钥写进共享脚本。-N 关闭 curl 的输出缓冲,便于观察片段到达;持续流被测试时间上限终止,不必直接判成服务故障。
记录首个片段出现时间、片段间隔和结束方式。如果直连也一次性返回,先查应用;只有代理后攒包,再检查代理、压缩和上层 CDN。
WebSocket 应使用实际客户端完成握手和消息收发,确认返回升级状态并能双向通信。普通 HTTP GET 返回 200 不能证明 WebSocket 已经工作。
不要自动重放有副作用的生成请求
模型调用可能计费,也可能触发工具、写数据库或创建任务。上游断开之后,如果网关、客户端和 SDK 各重试一次,实际请求次数可能超出预期。
为这类接口设计幂等键、任务标识与状态查询,明确哪一层负责重试。已经开始向客户端发送响应后,不能假装可以透明切换上游并拼接另一个模型的输出。
取消也需要贯穿链路:用户关闭页面后,应用是否停止等待、释放连接并取消上游工作?否则界面看似结束,后台仍可能继续消耗资源。
修改、验证与恢复
改配置前保存原文件,通过语法检查后再重载:
sudo nginx -t
sudo systemctl reload nginx
除了流式接口,还要回归普通 API、登录、上传和 WebSocket。确认没有把测试路径误匹配到其他请求,也没有缓存私人回答。
出现异常时恢复原配置,重新通过语法检查再重载。若已经对公网提供服务,应事先安排验证窗口,不把一系列超时与缓冲开关同时改动,避免不知道哪项导致变化。
最终验收包括首片段、持续输出、正常结束、用户取消、上游错误与空闲中断。只有完整走过这些状态,才能说明代理适合这条 AI 应用链路。


