现在很多小伙伴都会折腾各种大模型相关的服务,比如自建大模型 API 聚合网关(如 CLIProxyAPI)。
但在用 Nginx 反代大模型接口时,很多朋友往往照搬常规的 Web
反代模板,结果一测流式传输就傻眼了——明明请求参数里传了
"stream": true,但前端打字机效果完全失效,不仅没有丝滑的一个字一个字往外蹦,反而像是卡住死机了一样,直到后端模型全部生成完毕或者憋够了一大段,才瞬间“哗啦”一下子全部吐出来。
其实问题就出在 Nginx 的一个默认设置上,今天特此记录一下这个坑点及解决办法。
罪魁祸首:Nginx 的响应缓冲(proxy_buffering)
大模型的流式输出主要依赖 SSE(Server-Sent
Events,服务器发送事件) 协议。SSE 本质上是一个保持开启的 HTTP
长连接(Content-Type: text/event-stream),后端每推理生成一个
token,就会以小数据块(chunk)的形式即时向客户端推送一段数据。
而 Nginx 作为高性能反向代理,默认情况下它的配置是:
1 | proxy_buffering on; |
1. 为什么 Nginx 默认开启缓冲?
在传统 Web 场景下,开启缓冲是为了提高网络吞吐效率并减轻后端应用服务器的压力。Nginx 会尽可能快地把后端吐出来的响应全部读进自己的内存缓冲区(如果数据太大还会暂存到磁盘临时文件),等攒满一个 buffer 或者后端响应全部完成之后,再慢慢发给网络条件参差不齐的客户端。
2. 如果不关掉它,会是什么表现?
对传统网页来说这是极好的优化,但对于 SSE 流式接口来说,这就成了灾难:
- 假死式等待:后端大模型吐出的每一个 SSE event 通常只有几十到上百字节。Nginx 收到后不会立刻发给前端,而是默默把它扣在自己的缓冲区里等待“凑整”。
- 卡顿/憋大招:客户端在屏幕前只能看着光标干转圈,等 Nginx 的缓冲区(通常是 4k / 8k / 16k)终于被填满了,才会把这一个缓冲区的数据一次性推给客户端。
- 打字机变复印机:对于输出较短的回答,甚至可能一直等到后端大模型完全结束会话、关闭连接,Nginx 才把整篇回答当成一次普通响应直接甩给前端,流式交互体验荡然无存。
解决办法:明确禁用响应缓冲
解决这个问题非常简单,只需要在 Nginx 对应的 location
块中加入一行关键配置:
1 | proxy_buffering off; |
它的作用就是彻底关闭 Nginx
对后端上游响应的缓冲机制。一旦设置为 off,Nginx
只要从后端大模型服务收到任何数据块(哪怕只有几个字节),就会立刻同步转发给客户端,打字机流式效果瞬间恢复丝滑!
完整的大模型反代配置参考
除了
proxy_buffering off;,反代大模型接口时通常还需要兼顾长连接、超时时间等问题,这里整理了一份实用、不易踩坑的完整
location 配置供参考:
1 | location / { |
补充要点:
proxy_read_timeout 600s;:现在的大模型(尤其是带 Thinking / Reasoning 的推理模型)在思考阶段可能会有相当长一段时间不吐字,Nginx 默认的proxy_read_timeout只有 60 秒,很容易在模型还在思考时就被 Nginx 主动掐断连接报 504 Gateway Time-out,因此务必把读超时调大。关于超时的详细分析与设置要点,可以参阅我之前写的这篇文章:解决Nginx反向代理OpenAI Response协议60秒超时中断的问题。X-Accel-Buffering: no:有些服务端程序会在响应头中主动附带X-Accel-Buffering: no,通知 Nginx 临时关闭缓冲;但自己配置 Nginx 时,直接写死proxy_buffering off;最为稳妥,不依赖后端实现的自觉性。- 前置 CDN 注意事项:如果你的 Nginx 前面还挂了 Cloudflare 等 CDN,记得在 Cloudflare 控制台或 WAF 规则里也要注意关闭压缩与缓冲,或者确认你的套餐对长连接 SSE 的超时支持情况。
修改配置后执行 nginx -t && nginx -s reload
重载配置即可生效,收工~