bigywater.tech
Flask · 169

大文件响应性能

Flask 发大文件该怎么发?send_file 与整个读进内存、手写生成器差多少?断点续传支持吗?让 nginx 来发文件有什么好处?这篇用 300 MiB 的文件、真实的 gunicorn 与 nginx 把答案量出来。

+3 vs +303 MB
下载 315 MB 文件时 worker 多用的内存:send_file 与整个读进内存
206 与 304
send_file 对 Range 与 If-None-Match 请求的状态码
170 → 2 ms
大文件请求占用 gunicorn worker 的时间:nginx 代理 send_file 与 X-Accel-Redirect

三种发文件的方式

实测 · 本机执行输出
  send_file(sendfile)        200 用时  0.07 秒 | 速度   4375 MB/s | 收到 315 MB | worker 空闲内存 64 MB,下载期间峰值 68 MB
  整个文件读进内存再返回                200 用时  0.12 秒 | 速度   2528 MB/s | 收到 315 MB | worker 空闲内存 65 MB,下载期间峰值 368 MB
  64 KB 一块的生成器               200 用时  0.11 秒 | 速度   2774 MB/s | 收到 315 MB | worker 空闲内存 65 MB,下载期间峰值 68 MB

上一篇:缓存命中率;下一篇:压测下的全局状态问题。

文件在本机页缓存里,没有磁盘读取的限制,速度数字偏高,只看相对关系。

Range 与条件请求

实测 · 本机执行输出
  状态行: HTTP/1.1 206 PARTIAL CONTENT | Content-Range: bytes 313524224-314572799/314572800 | Content-Length: 1048576 | Accept-Ranges: bytes | ETag: True
  带 If-None-Match 的再次请求 → 304 (304,不再传文件)

nginx 发文件

实测 · 本机执行输出
  经 nginx 代理 Flask 的 send_file       200 用时  0.97 秒 |    325 MB/s | 收到 315 MB | gunicorn 日志里这个请求占用 worker 170 ms
  X-Accel-Redirect 让 nginx 直接发文件     200 用时  0.94 秒 |    334 MB/s | 收到 315 MB | gunicorn 日志里这个请求占用 worker 2 ms

nginx 是自己起的实例(自签证书),internal location 用 alias 指向文件所在目录。

分步演示

大文件响应性能
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动。gunicorn gthread、nginx 1.31.6(自己的配置、非默认端口)、curl 8.7.1
    • 没有验证:冷缓存下的磁盘读取速度、慢速客户端下 worker 被占用的时长差别、sendfile 在 gunicorn 各 worker 类型下的差别(这里只测了 gthread)、HTTP/2、对象存储的预签名链接方案、Content-Disposition 与中文文件名(第 125 篇看过)
    京ICP备2026067502号-1京公网安备11010502063487号