bigywater.tech
微服务 · 24

缓冲:proxy_buffering 开关对流式响应的影响

proxy_buffering 开着,流式响应会不会被攒住?慢 client 到底占用谁的资源?gzip 对 SSE 又有什么影响?这篇用一个能控制节奏的上游和 nginx 1.31.6 量出来,和常见的说法对一对。

0.00 s
proxy_buffering 开着(默认)时,SSE 式流的第一块到达 client 的时刻;打开 gzip 后是 2.09 s
0.0 s 对 9.2 到 10.1 s
40 MB 响应遇到慢 client,上游把它写完用的时间:缓冲打开 对 proxy_max_temp_file_size 0 / buffering off
38.9 MB
缓冲打开时 nginx worker 为这个慢 client 打开的临时文件的大小(目录项已被 unlink,用 lsof 量到)

流式响应

实测 · 本机执行输出
  数据块大小    | 配置                                 | 收到第一批响应体 | 收到一半字节 | 收完     | recv 次数
  ---------+------------------------------------+----------+--------+--------+--------
  100 字节   | buffering on(默认)                   | 0.00 s   | 1.04 s | 2.07 s | 11     
  100 字节   | buffering off                      | 0.00 s   | 1.03 s | 2.06 s | 11     
  100 字节   | on + 上游发 X-Accel-Buffering: no     | 0.00 s   | 1.03 s | 2.06 s | 11     
  100 字节   | on + gzip on(压缩 text/event-stream) | 2.09 s   | 2.09 s | 2.09 s | 1      
  16384 字节 | buffering on(默认)                   | 0.00 s   | 1.04 s | 2.07 s | 11     
  16384 字节 | buffering off                      | 0.00 s   | 1.04 s | 2.08 s | 14     
  16384 字节 | on + 上游发 X-Accel-Buffering: no     | 0.00 s   | 1.04 s | 2.07 s | 12     
  16384 字节 | on + gzip on(压缩 text/event-stream) | 2.06 s   | 2.06 s | 2.06 s | 1      

缓冲打开时 nginx 也会随到随转发,小块不会被攒到最后;gzip 才会。

慢 client

实测 · 本机执行输出
  配置                              | 上游写完 40 MB 用了 | client 收完用了 | worker 打开的临时文件最大
  --------------------------------+---------------+-------------+-----------------
  buffering on(默认)                | 0.0 s         | 11.3 s      | 38.9 MB         
  on + proxy_max_temp_file_size 0 | 9.2 s         | 11.3 s      | 0.0 MB          
  buffering off                   | 10.1 s        | 11.3 s      | 0.0 MB          
  缓冲打开且允许写临时文件时,看“上游写完”那一列:nginx 把响应整个收下来(放进 proxy_temp_path 下的临时文件,最后一列是它的大小;nginx 打开后立刻 unlink 目录项,目录里看不到,所以用 lsof 量),上游连接早早释放,client 再慢也只占 nginx 的磁盘。不允许写临时文件或关掉缓冲时,上游被拖到接近 client 的速度(上游写完的时间接近 client 收完的时间,差的部分是内核 socket 缓冲吸收的)

缓冲打开时 nginx 用临时文件吸收整个响应,上游很快释放;关掉缓冲上游被占到 client 收完。

分步演示

缓冲 proxy_buffering
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Go / nginx / Envoy / etcd / Consul / Nacos / OpenTelemetry / Prometheus(按各篇注明用到哪些及版本);所有服务都是本机自己起的进程,端口 9150 以上,只监听 127.0.0.1,不连任何外部服务,不碰本机已有的实例;故障用 kill -9 进程、让连接断开或在代码里注入延迟与错误来制造;计时用 time.perf_counter,数字每次运行会有小幅抖动。nginx 是 Homebrew 的 1.31.6;上游是我写的原始 TCP 服务(chunked 流式响应、一次性写 40 MB);client 是原始 socket,慢 client 每 10 ms 读 50 KB;临时文件大小用 lsof 看 worker 进程打开的 proxy_temp_path 下的文件(我最初扫目录得到 0,是因为 nginx 打开后立刻 unlink 了目录项);“收到一半字节”按接收的字节累计;每种配置只测一次
    • 没有验证:旧版本 nginx 在缓冲打开时对 SSE 的行为(一些资料说会攒住,这里没有观察到);proxy_buffers 数量与大小对内存的影响;gzip 配合上游主动 flush 的情形;HTTP/2 client 的流控对缓冲的影响;X-Accel-Buffering 头在这个实验里没有表现出可见的差别,因为缓冲打开时本来就没有被攒住
    京ICP备2026067502号-1京公网安备11010502063487号