REST流式传输大文件遇DataBufferLimitException问题排查
问题根源
你的代码没实现真正的流式传输,核心问题出在findById方法里:
webClient.toEntity(InputStreamResource.class).block()会让WebClient把后端返回的整个文件内容全部加载到内存中,再封装成InputStreamResource返回。这就导致大文件直接触发DataBufferLimitException——因为Spring WebClient默认的响应体缓冲上限就是256KB。- 后面的
StreamingOutput只是把内存里的数据流出去,本质还是先下载完整文件再转发,没有达到流式传输的目的。
修复方案
要实现真正的流式转发,需要让WebClient以数据流(Flux<DataBuffer>)的形式获取后端响应,再直接把数据流传递给客户端,全程不把完整文件加载到内存。
修改后的完整代码
@Component @Api @PreAuthorize("hasAuthority('USER')") @RequiredArgsConstructor public class AttachmentApiImpl implements AttachmentApi { private static final Logger LOGGER = LoggerFactory.getLogger(AttachmentApiImpl.class); private final WebClient webClient; @Override public Response getAttachment(String id) { // 同时获取响应头和数据流,不提前加载整个文件 Pair<HttpHeaders, Flux<DataBuffer>> responseData = fetchAttachmentStream(id).block(); if (responseData == null) { throw new WebApplicationException("Attachment not found", Response.Status.NOT_FOUND); } HttpHeaders backendHeaders = responseData.getFirst(); Flux<DataBuffer> dataBufferFlux = responseData.getSecond(); // 实现流式输出,逐块写入客户端响应 StreamingOutput streamingOutput = output -> { DataBufferUtils.write(dataBufferFlux, output) // 错误时释放未处理的DataBuffer,避免内存泄漏 .doOnError(error -> { DataBufferUtils.release(dataBufferFlux); LOGGER.error("Failed to stream attachment", error); throw new WebApplicationException("Error streaming file", error); }) // 阻塞直到整个流传输完成 .block(); }; return Response.ok(streamingOutput) .header(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.ETAG + ", " + HttpHeaders.CONTENT_DISPOSITION) .header(HttpHeaders.ETAG, backendHeaders.getETag()) .header(HttpHeaders.CONTENT_DISPOSITION, backendHeaders.getContentDisposition().toString()) .header(HttpHeaders.CONTENT_TYPE, backendHeaders.getContentType().toString()) .build(); } private Mono<Pair<HttpHeaders, Flux<DataBuffer>>> fetchAttachmentStream(String id) { return webClient.get() .uri("/attachments/{id}", id) .exchangeToMono(response -> { // 获取后端响应头 HttpHeaders headers = response.headers().asHttpHeaders(); // 以数据流形式获取响应体,不缓冲到内存 Flux<DataBuffer> bodyStream = response.bodyToFlux(DataBuffer.class); return Mono.just(Pair.of(headers, bodyStream)); }); } }
关键改动说明
- 改用
exchangeToMono获取响应头和数据流- 避免
toEntity或retrieve().bodyToMono()这类会把完整响应加载到内存的方法,直接通过bodyToFlux(DataBuffer.class)获取流式响应体。
- 避免
- 用
DataBufferUtils处理数据流DataBufferUtils.write可以把Flux<DataBuffer>直接写入输出流,实现逐块传输,全程不加载完整文件到内存。
- 添加DataBuffer释放逻辑
- 在错误时调用
DataBufferUtils.release,避免未消费的DataBuffer占用内存,导致泄漏。
- 在错误时调用
额外优化建议
- 不要在WebClient调用中使用
block():如果你的JAX-RS框架支持异步响应,可以改为返回CompletionStage<Response>,用Mono的异步操作替代阻塞调用,提升并发性能。 - 调整WebClient的超时设置:大文件传输耗时较长,可能需要增大WebClient的连接超时和读取超时,避免中途断开。
内容的提问来源于stack exchange,提问作者rockarolla
相关产品推荐
相关产品推荐

