一、前言

在日常系统开发中,文件下载是非常常见的一类功能,例如:

  • 导出报表
  • 下载附件
  • 下载压缩包
  • 下载模板文件
  • 下载安装包

很多项目会基于Spring MVC 静态资源映射能力实现下载功能,例如:

registry.addResourceHandler("/download/**")
        .addResourceLocations("file:/data/files/");

用户通过:

/download/a.zip

即可直接下载服务器文件。

这类接口与普通 JSON 业务接口存在明显区别:

  • 通常没有复杂业务逻辑
  • 不涉及大量数据库操作
  • 更多消耗网络、磁盘、IO 和连接资源

因此文件下载接口的性能测试思路与普通接口 TPS 压测并不完全相同。

本文将结合一个典型场景:

“一个页面存在两个不同文件下载链接”

系统讲解:

  • Spring MVC 静态资源下载接口的特点与性能测试思路
  • 下载类接口需要重点关注的核心指标
  • 缓存机制对下载性能测试结果的影响
  • 多文件下载场景下的性能测试方案设计

二、Spring MVC 静态资源映射接口简介

Spring MVC 静态资源映射,本质上是:

将某个 URL 路径映射到服务器文件目录。

例如:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        registry.addResourceHandler("/download/**")
                .addResourceLocations("file:/data/files/");
    }
}

此时:

/download/a.zip

会映射到:

/data/files/a.zip

这种方式实现简单,适用于:

  • 公共文件下载
  • 模板文件下载
  • 安装包下载
  • 静态资源下载

其特点是:

  • 业务逻辑较少
  • 请求处理链路较短
  • 更依赖服务器 IO 与网络能力

三、下载接口与普通业务接口的区别

很多人在做性能测试时,会默认关注:

  • TPS
  • QPS
  • 响应时间

但对于下载类接口而言,真正关键的指标往往并不是 TPS。

1. 普通业务接口

例如:

/api/user/list

通常:

  • 消耗 CPU
  • 消耗数据库连接
  • 涉及业务计算
  • 涉及缓存查询

性能瓶颈通常在:

  • CPU
  • 数据库
  • Redis
  • JVM GC

2. 文件下载接口

例如:

/download/a.zip

通常:

  • 不涉及复杂计算
  • 不涉及数据库
  • 更依赖文件读取与网络传输

性能瓶颈通常在:

资源类型 是否关键
网络带宽 非常关键
网卡吞吐 非常关键
TCP连接数 非常关键
磁盘 IO 关键
Tomcat/Nginx 连接数 关键
CPU 反而可能不高
JVM GC 通常不是重点

因此,下载接口性能测试,本质上更偏向:

IO 与网络吞吐测试。


四、下载接口的数据流转过程

理解下载压测之前,需要先理解一次下载请求在服务器内部的执行过程。

用户请求:

/download/a.zip

服务器内部通常经历:

磁盘 -> 内存 -> 网卡 -> 网络 -> 用户

具体过程如下:

1. 磁盘读取文件

服务器从:

/data/files/a.zip

读取文件。

此时会产生:

  • 磁盘 IO
  • 文件读取操作

2. 文件进入内存

文件读取后:

会进入 Linux 内存缓存(Page Cache)。

后续再次访问时:

可能无需再次读取磁盘。


3. 数据通过网卡发送

服务器最终通过:

  • 网卡
  • 网络带宽

将文件发送给客户端。

因此,下载接口本质上是:

服务器持续向外发送大量数据。


五、下载接口性能测试的重点指标

下载类接口的重点指标通常包括:

指标 说明
下载成功率 是否存在超时、断连
平均下载耗时 下载整体时长
网络吞吐量 MB/s、Gbps
并发连接数 TCP连接占用情况
磁盘 IO 文件读取压力
网卡带宽利用率 是否打满带宽
Tomcat/Nginx 连接数 是否达到上限

其中网络吞吐量与带宽利用率通常是重点。


六、下载接口中的缓存机制与性能测试影响

下载类接口性能测试中,一个非常容易被忽略的问题就是:

缓存会直接影响压测结果。

尤其是基于 Spring MVC 静态资源映射实现的文件下载接口,通常都会受到 Linux 文件缓存机制的影响。

如果不理解缓存机制,很容易出现:

第一次压测很慢
后面压测越来越快

但实际上系统性能未必真的提升了。


1. 下载请求的数据流转过程

以:

/download/a.zip

为例。

一次文件下载请求,在服务器内部通常会经历:

磁盘 -> 内存 -> 网卡 -> 网络 -> 用户

具体过程如下:

阶段 说明
磁盘 读取真实文件
内存 文件进入 Linux Page Cache
网卡 将数据发送到网络
用户 客户端接收文件

其中:

  • 磁盘读取速度最慢
  • 内存读取速度远高于磁盘
  • 网卡负责真正向外发送数据

因此,如果文件已经进入内存缓存,后续下载性能会明显提升。


2. 什么是热缓存(Warm Cache / Hot Cache)

第一次下载文件时:

磁盘 -> 内存 -> 网卡

系统需要真实读取磁盘文件。

但 Linux 会自动将热点文件缓存到:

OS Page Cache

后续再次下载时可能直接变成:

内存 -> 网卡

无需再次读取磁盘。这种场景称为:

热缓存场景(Warm Cache / Hot Cache)


3. 热缓存对压测结果的影响

进入热缓存后系统通常会出现:

现象 原因
下载速度提升 不再读取磁盘
响应时间下降 直接命中内存
磁盘 IO 降低 文件已缓存
CPU 变化不明显 下载主要消耗 IO 与网络

因此,很多下载文件的性能测试最终测到的实际上是:

内存缓存能力 + 网络吞吐能力

而不是:

磁盘读取能力。


4. 压测中的缓存到底是在压测机还是被测机

这是下载压测中最容易混淆的问题。很多人会误以为:

后面的请求变快
是不是压测机缓存了文件

实际上,对于 Apache JMeter 这类压测工具而言,通常只是不断发起 HTTP 请求。

真正发生缓存的位置通常是:

缓存位置 是否关键
被测机 OS Page Cache 非常关键
Nginx Cache 非常关键
CDN Cache 非常关键
压测机缓存 一般不关键

也就是说即使是:

500个不同用户同时下载同一个文件

Linux 仍然只需要:

第一次读磁盘

后续所有请求都可以共享同一份:

Page Cache

因此,即使模拟的是“不同用户”,也依然会命中热缓存。这是 Linux 操作系统的正常行为。


5. 为什么热缓存是正常且合理的生产场景

很多人会担心:

命中缓存是不是压测不真实

实际上真实生产环境中热点文件本来就会被缓存。

例如:

缓存类型 场景
Linux Page Cache 高频下载文件
Nginx Cache 静态资源
CDN Cache 公网文件下载

因此,对于以下场景:

  • 页面文件下载
  • 模板下载
  • 安装包下载
  • 高频附件下载

通常更推荐:

热缓存场景压测。

因为这更符合真实生产环境。


6. 如何判断当前是否命中缓存

(1)观察磁盘 IO

推荐使用:

iostat -x 1

重点关注:

指标 含义
rkB/s 磁盘读吞吐
await IO等待时间
util 磁盘利用率

例如:

现象 说明
rkB/s 很高 正在读磁盘
rkB/s 很低 可能命中缓存

(2)观察 Linux Cached

使用:

free -m

或者:

cat /proc/meminfo

观察:

Cached

字段。

如果下载后:

Cached 持续增长

通常说明文件已经进入:

Page Cache

(3)观察首次与后续下载耗时差异

如果:

第一次下载明显较慢
后续下载明显变快

通常说明后续请求已经命中缓存。


(4)判断是否存在 Nginx/CDN 缓存

如果系统前面存在:

  • Nginx
  • CDN
  • 网关缓存

还需要进一步确认请求是否已经被上层缓存拦截。

例如:

curl -I https://xxx/download/a.zip

观察响应头:

Header 说明
X-Cache: HIT 已命中缓存
Age 缓存时间
Via 代理/CDN信息

七、下载接口如何进行性能测试

1. 压测工具选择

通常使用 Apache JMeter 对下载接口进行并发压测,用于模拟:

  • 多用户同时下载
  • 长连接占用场景
  • 大文件持续传输
  • 网络带宽压力场景

下载类接口本质是“数据流传输”,因此工具重点在于稳定发起请求并持续接收响应,而不是复杂业务处理能力。


2. JMeter 请求配置

(1)使用 HTTP Request 直接访问下载地址

例如:

GET /download/a.zip

无需额外参数处理,按真实用户访问方式发起请求即可。


(2)禁止保存响应内容

下载接口返回通常为:

  • ZIP
  • PDF
  • Excel
  • 大文件流

如果开启以下功能:

  • 保存 Response Data
  • View Results Tree
  • Debug Sampler

会导致:

问题 原因
JMeter 内存占用暴涨 响应内容被完整加载
CPU 占用升高 大文件解析与写入
压测机磁盘压力增加 结果文件落盘

最终结果是:

压测瓶颈从“被测系统”转移到“压测机”。

因此建议:

  • 关闭响应体保存
  • 禁用 View Results Tree
  • 仅保留基础统计报告(Summary / Aggregate Report)

(3)下载结果校验方式

下载接口通常不返回业务 JSON 响应体,因此其正确性校验一般不能依赖业务字段,而是根据不同测试目标采用不同粒度的校验方式。


HTTP 状态码校验(基础必选)

用于判断请求是否正常到达服务端并完成处理。

常见状态码包括:

  • 200:下载成功
  • 4xx:权限不足、参数错误或资源不存在
  • 5xx:服务端处理异常

该校验是所有下载接口压测的基础条件,主要用于识别请求是否成功完成。


响应大小校验(常用校验方式)

通过校验响应文件大小判断下载结果是否异常,主要用于识别以下问题:

  • 返回错误页面(通常只有几 KB)
  • 下载过程中发生中断或截断
  • 文件未完整传输
  • 返回内容与预期文件明显不符

该方式实现成本低,对压测机性能影响较小,适用于大多数性能压测、稳定性测试及高并发下载场景。

对于静态资源下载接口而言,响应大小校验通常已经能够覆盖绝大多数异常情况,因此也是实际项目中最常用的下载结果校验方式。


MD5 哈希校验(内容一致性校验)

在 Apache JMeter 中可通过:

Save response as MD5 hash

对响应内容计算 MD5 哈希值,并与预期结果进行比对,用于校验文件内容是否完全一致。

该方式主要用于验证:

  • 文件内容是否发生变化
  • 是否出现状态码为 200 但文件内容异常的情况
  • 是否发生文件截断但文件大小仍然正常
  • 是否被错误页面、异常内容或其他文件替换

MD5 校验属于内容级一致性校验,相比响应大小校验准确性更高,能够发现仅通过文件大小无法识别的问题。

适用场景包括:

  • 生产验收测试
  • 文件内容一致性要求较高的场景
  • 安装包、配置文件、报表导出等关键文件下载
  • CDN、网关或网络链路稳定性验证场景

需要注意的是,MD5 校验需要 JMeter 对完整响应内容进行额外哈希计算。在大文件、高并发或长时间压测场景下,可能增加压测机 CPU 开销,从而影响压测机自身性能。因此实际项目中通常不会在高并发主压测阶段对所有请求启用 MD5 校验,而是采用:

  • HTTP 状态码校验
  • 响应大小校验
  • 少量 MD5 抽样校验

结合使用的方式,在保证压测性能的同时兼顾内容一致性验证。


八、页面存在两个不同文件下载链接时如何测试

假设页面中存在:

下载链接 文件大小
文件A 1MB
文件B 500MB

并且:

  • 每次点击只下载一个文件
  • 两个链接对应两个独立接口

那么该场景本质上是:

同一个页面下的两个独立下载接口。


1. 为什么必须分别测试两个接口

因为文件大小不同后性能特征会完全不同。


(1)小文件下载特点

例如:

1MB

更容易表现为:

特征 表现
TPS 较高
请求频繁
连接建立频繁
Tomcat线程切换频繁

(2)大文件下载特点

例如:

500MB

更容易表现为:

特征 表现
带宽占用大
下载耗时长
TCP连接长期占用
磁盘 IO 更明显

因此必须分别压测:

/download/fileA
/download/fileB

2. 为什么还推荐混合压测

虽然两个接口需要单独测试。但真实生产中两个下载接口最终会共享:

  • 网络带宽
  • 网卡
  • Nginx
  • Tomcat连接
  • 磁盘 IO

因此还需要模拟真实用户行为。

例如:

下载类型 用户占比
文件A 70%
文件B 30%

模拟:

  • 一部分用户下载小文件
  • 一部分用户下载大文件

观察:

  • 带宽是否被大文件占满
  • 小文件响应是否被拖慢
  • 并发连接是否耗尽

这类测试通常称为:

混合场景压测。


3. 推荐的完整测试方案

建议分三个阶段进行。


第一阶段:文件A单独压测

验证:

  • 小文件并发能力
  • TPS
  • Tomcat连接能力

第二阶段:文件B单独压测

验证:

  • 大文件带宽能力
  • 网络吞吐能力
  • 长连接稳定性

第三阶段:混合场景压测

按真实比例混合:

  • 文件A下载
  • 文件B下载

验证:

  • 不同类型下载是否互相影响
  • 系统整体稳定性
  • 带宽竞争情况

九、总结

Spring MVC 静态资源下载接口的性能测试,与普通业务接口不同,重点通常不在数据库或业务逻辑,而是在网络带宽、网卡吞吐、TCP连接数以及磁盘 IO 等资源。

下载请求在服务器内部通常会经历“磁盘 -> 内存(Page Cache) -> 网卡 -> 用户”的过程,因此下载压测很容易受到 Linux Page Cache 的影响。很多情况下,第一次请求会真实读取磁盘,后续请求则会进入热缓存场景,直接从内存返回数据。所以在下载压测中,需要明确当前测试属于冷缓存还是热缓存场景,而页面下载、高频文件下载等场景通常更推荐采用热缓存压测,因为这更符合真实生产环境。

对于“一个页面存在两个不同文件下载链接”的场景,建议采用“单接口压测 + 混合场景压测”的方式。先分别测试不同文件的下载接口,分析小文件高并发与大文件长连接、带宽占用等差异,再结合真实用户比例进行混合压测,验证系统在真实下载场景下的整体稳定性。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐