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

Zuul作为API网关时大文件流式下载的堆内存及性能问题排查

解决Zuul网关大文件流式下载内存缓存问题

我之前也遇到过完全一样的问题——直接调用后端服务时大文件流式下载很顺畅,但通过Zuul转发就必须等Zuul把整个文件加载到内存后才开始向客户端传输,GB级文件直接把堆内存撑爆,还引发了一堆性能瓶颈。经过排查,问题确实出在Spring整合Zuul时的默认响应处理逻辑上,它会把响应体全部缓存到内存中,而不是直接透传字节流。

下面是我验证过的解决方案,分三个核心步骤:


1. 自定义Zuul Post过滤器,直接透传响应流

我们需要绕过Zuul默认的响应体处理逻辑,在响应返回客户端前直接把后端的输入流转发到客户端的输出流,全程不缓存到内存。

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;

@Component
public class StreamingResponseFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return "post"; // 后置过滤器,专门处理响应
    }

    @Override
    public int filterOrder() {
        return -1; // 优先级高于默认的响应处理过滤器,确保先执行
    }

    @Override
    public boolean shouldFilter() {
        // 可以根据实际场景调整判断逻辑,比如只针对下载请求
        RequestContext ctx = RequestContext.getCurrentContext();
        return ctx.getResponse().getContentType() != null 
            && ctx.getResponse().getContentType().startsWith("application/octet-stream");
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletResponse response = ctx.getResponse();
        
        try (InputStream backendInputStream = ctx.getResponseDataStream();
             OutputStream clientOutputStream = response.getOutputStream()) {
            
            byte[] buffer = new byte[4096]; // 4KB缓冲区,平衡性能和内存占用
            int bytesRead;
            while ((bytesRead = backendInputStream.read(buffer)) != -1) {
                clientOutputStream.write(buffer, 0, bytesRead);
                clientOutputStream.flush(); // 强制刷新,确保数据实时发送到客户端
            }
            
            // 告诉Zuul不再处理响应体,避免重复操作
            ctx.setSendZuulResponse(false);
        } catch (Exception e) {
            // 建议替换为项目的日志框架记录异常
            e.printStackTrace();
        }
        return null;
    }
}

2. 调整Spring & Zuul配置,适配流式传输

在配置文件中添加以下设置,避免异步超时、调整连接池大小,确保流式传输的稳定性:

zuul:
  sensitive-headers: Cookie,Set-Cookie # 根据业务需求调整敏感头
  host:
    max-total-connections: 200 # 全局最大连接数,根据服务器性能调整
    max-per-route-connections: 50 # 单路由最大连接数
spring:
  mvc:
    async:
      request-timeout: -1 # 禁用异步请求超时,防止大文件传输被中断

3. 确保后端服务返回真正的流式响应

后端服务不能把文件一次性加载到内存再返回,必须使用StreamingResponseBody或者直接操作输出流:

@GetMapping("/download-large-file")
public ResponseEntity<StreamingResponseBody> downloadLargeFile() {
    StreamingResponseBody responseBody = outputStream -> {
        // 实际场景中替换为你的文件输入流(比如从OSS、本地文件系统读取)
        try (InputStream fileInputStream = new FileInputStream("/path/to/your/large-file.bin")) {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = fileInputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
                outputStream.flush();
            }
        }
    };

    return ResponseEntity.ok()
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"large-file.bin\"")
            .body(responseBody);
}

原理说明

默认情况下,Zuul的SendResponseFilter会把后端返回的响应体转换成byte[]存储在内存中,这对小文件没问题,但GB级文件直接会耗尽堆内存。我们自定义的过滤器在SendResponseFilter之前执行,直接把后端的输入流透传给客户端,全程只占用固定大小的缓冲区内存,彻底解决大文件下载的内存瓶颈。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 09:11:13