StreamingHttpResponse 是「边生成边发」还是「只是换了个写法」?这篇在真实的 gunicorn 上量首字节时间和大响应的内存占用,再看它跟普通响应在属性上的不同。
/buffered/ 首字节 1507 ms,收完 1532 ms,共 40 字节;Content-Length: 40 Transfer-Encoding: None /streaming/ 首字节 304 ms,收完 1508 ms,共 40 字节;Content-Length: None Transfer-Encoding: chunked
gunicorn 同步 worker,一个 worker。客户端用 read1 读取,记录第一个字节到达的时刻(用 read(n) 会因为凑不够 n 字节而被自己拖慢,这是我第一次量时踩的坑)。总耗时都是 ≈1.5 s——流式改变的是「用户什么时候开始看到内容」,不是总时间。
streaming = True r.content → AttributeError: This StreamingHttpResponse instance has no `content` attribute. Use `streaming_content` instead. Content-Length 头: False streaming_content 类型: map
流式响应没有 .content,只能迭代 streaming_content;中间件里如果读 response.content 就会炸,所以对流式响应要判断 response.streaming。
/big_buffered/ worker 起始 RSS ≈ 41 MiB;收完 126 ms,首字节 26 ms,200 MiB;峰值 RSS ≈ 241 MiB;Content-Length: 209715200 /big_streaming/ worker 起始 RSS ≈ 41 MiB;收完 98 ms,首字节 3 ms,200 MiB;峰值 RSS ≈ 43 MiB;Content-Length: None
每种方式都用全新的 worker 进程测(RSS 在释放后不一定回落,共用一个进程会污染下一次测量——我第一次就这样量错了)。流式方式内存基本不涨。
大文件下载、导出、服务端事件推送。注意:压缩(GZipMiddleware)对流式响应的处理、反向代理是否会缓冲(nginx 的 proxy_buffering)都会影响「首字节」效果,这两点本文没有测。同步 worker 下,一个慢的流式响应会占住整个 worker(本文只用了 1 个 worker)。