You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring WebFlux流式文件上传耗时过长问题排查及解决

问题分析与解决方案

核心原因

你遇到的情况确实是因为Spring WebFlux默认会先全量读取并预处理整个上传文件,再将FilePart对象发射到Mono<FilePart>中。这就导致你的flatMap断点要等整个文件上传完成后才会触发,实际耗时远超过带宽计算的理论值——因为框架在你代码执行前就已经完成了全文件接收。

默认的DefaultPartHttpMessageReader会将上传的文件内容全部读取到内存(或超过内存阈值时写入临时文件),之后才会生成FilePart实例交给业务代码,完全失去了响应式流式处理的优势。

解决方法

要实现真正的流式上传(边接收边处理,无需等待全文件上传完成),你需要直接接收文件的数据流,而非等待封装好的FilePart。具体有两种方案:

方案1:直接接收Flux<DataBuffer>

修改接口参数,用Flux<DataBuffer>接收文件内容,配合@RequestPart获取文件元信息,这样可以在文件上传过程中就开始处理数据:

@PostMapping(value = "upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<UploadResponse> upload(
        @RequestHeader("X-My-Meta-Info-Header") final UUID id,
        @RequestPart("file") Flux<DataBuffer> fileDataFlux,
        @RequestPart("file") final Part filePart) {
    final long start = System.currentTimeMillis();
    return fileStorageService.store(id, fileDataFlux, filePart.filename())
            .doOnSuccess(f -> log.info("Uploading file took {}ms",
                    System.currentTimeMillis() - start))
            .map(f -> UploadResponse.builder()
                    .id(id)
                    .originalFileName(filePart.filename())
                    .build());
}

对应的fileStorageService.store需要调整为接收Flux<DataBuffer>,直接流式写入存储:

public Mono<Void> store(UUID id, Flux<DataBuffer> dataFlux, String filename) {
    Path filePath = Paths.get("/your/storage/path", id.toString() + "_" + filename);
    return DataBufferUtils.write(dataFlux, filePath, StandardOpenOption.CREATE)
            .doOnError(e -> DataBufferUtils.release(dataFlux))
            .then();
}

方案2:配置Multipart解析器(仅优化内存占用,无法解决耗时问题)

如果坚持使用FilePart,可以通过配置PartHttpMessageReader调整内存阈值,让大文件直接写入临时文件而非全量加载到内存,但这依然是先接收完文件才会触发你的代码,只能减少内存压力,无法缩短整体耗时。

配置示例:

@Bean
public HttpMessageReader<Part> partHttpMessageReader() {
    DefaultPartHttpMessageReader reader = new DefaultPartHttpMessageReader();
    // 设置内存阈值为1KB,超过则写入临时文件
    reader.setMaxInMemorySize(1024);
    return reader;
}

额外注意点

你代码中的计时逻辑存在误差:start变量在Mono创建时就初始化了,但Mono是懒订阅的,实际开始处理文件的时间可能晚于这个时间点。建议将计时放在flatMap内部:

return filePartMono.flatMap(filePart -> {
        long start = System.currentTimeMillis();
        return fileStorageService.store(id, filePart)
                .doOnSuccess(f -> log.info("Uploading file took {}ms",
                        System.currentTimeMillis() - start))
                .map(f -> UploadResponse.builder()
                        .id(id)
                        .originalFileName(filePart.filename())
                        .build());
    });

内容的提问来源于stack exchange,提问作者dtrunk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 18:13:13