【问题标题】:Prevent out of memory error with spring controller that accepts a file使用接受文件的弹簧控制器防止内存不足错误
【发布时间】:2018-04-07 18:05:20
【问题描述】:

假设我正在使用 Spring 设计一个 REST 服务,并且我需要一个接受文件并返回某种 ResponseDto 的方法。应用程序服务器的 POST 请求大小限制为 100MB。这是假设的弹簧控制器方法实现:

public ResponseEntity<ResponseDto> uploadFile(@RequestBody MultipartFile file) {
        return ResponseEntity.ok(someService.process(file));
} 

假设我的服务器有 64GB 的 RAM。如果在短时间内(足够短,process() 方法仍然为每个上传的文件运行),1000 个用户决定上传 100MB 文件(或仅 1用户同时上传 1000 个文件)?

编辑:澄清一下,我想确保我的应用程序不会崩溃,而是停止接受/延迟新请求。

【问题讨论】:

  • 您无需停止接受新请求。您只需要更有效地阅读文件。按照我在回答中的建议实施 BufferedReader 会更容易且对用户更友好。
  • 这看起来很酷,但是如果我需要一次将整个文件读入内存怎么办(例如,如果我需要验证校验和)?
  • 处理大文件不再像以前那样是个大问题,有很多解决方案。例如计算校验和(我编辑了我的答案以添加链接):docs.oracle.com/javase/7/docs/api/java/security/…

标签: java spring file upload out-of-memory


【解决方案1】:

您可以监控内存使用情况并查看何时必须停止接受请求或取消现有请求。 https://docs.oracle.com/javase/6/docs/api/java/lang/management/MemoryMXBean.html

https://docs.oracle.com/javase/6/docs/api/java/lang/management/MemoryPoolMXBean.html

你也可以用这个

Runtime runtime = Runtime.getRuntime();
System.out.println("Free memory: " + runtime.freeMemory() + " bytes.");

【讨论】:

  • 这看起来很酷。 Spring/Tomcat/其他应用服务器提供的这种机制是否有某种抽象?
  • 其实看层。如果我在你的位置,我会分析我是否需要像 tomcat 这样的容器,或者我会使用 Spring boot。基于此选择....这取决于您可以申请的地方。如何做出这个选择取决于....如果它只是一个 Web 服务,或者您也想提供其他东西,比如 JSP .. 范围有限的知识很难回答。
  • 如果你也想从外界监控它,那么试试stackoverflow.com/questions/6405518/…
【解决方案2】:

即使您可以在 64 GB RAM 的内存中保存许多文件,但您不希望用它浪费太多资源。读取文件有一些节省内存的方法,例如,您可以使用 BufferedReader,这是一种非常节省内存的读取文件的方法,因为它不会将整个文件存储在内存中。

documentation 很好地解释了它:

从字符输入流中读取文本,缓冲字符,以便高效读取字符、数组和行。 可以指定缓冲区大小,也可以使用默认大小。对于大多数用途,默认值足够大。

通常,由 Reader 发出的每个读取请求都会导致对底层字符或字节流发出相应的读取请求。因此,建议将 BufferedReader 包裹在 read() 操作可能成本高昂的任何 Reader 周围,例如 FileReaders 和 InputStreamReaders。例如,

BufferedReader 中 = new BufferedReader(new FileReader("foo.in"));

将缓冲来自指定文件的输入。如果没有缓冲,每次调用 read() 或 readLine() 都可能导致从文件中读取字节,转换为字符,然后返回,这可能非常低效。

这是另一个您可能会发现有用的 SO 问题:

Java read large text file with 70 million lines of text

如果您需要像您在 cmets 中所说的那样计算文件的校验和,您可以使用this link

【讨论】:

    【解决方案3】:

    考虑创建一个数据库表来保存正在完成的上传:

    CREATE TABLE PROC_FILE_UPLOAD 
    (
      ID NUMBER(19,0) NOT NULL 
    , USER_ID NUMBER(19,0) NOT NULL 
    , UPLOAD_STATUS_ID NUMBER(19,0) NOT NULL 
    , FILE_SIZE NUMBER(19,0)
    , CONSTRAINT PROC_FILE_UPLOAD_PK PRIMARY KEY (ID) ENABLE
    );
    
    COMMENT ON COLUMN PROC_FILE_UPLOAD.FILE_SIZE IS 'In Bytes';
    

    USER_ID 是您的用户表的 FK,UPLOAD_STATUS_ID 是具有不同应用程序状态的数据字典的 FK(IN_PROGRESSDONEERRORUNKNOWN,任何适合您的)。

    在您的服务上传文件之前,它必须检查当前用户是否已在上传文件,以及是否已达到最大并发上传数。如果是,则拒绝上传,否则使用新的上传更新PROC_FILE_UPLOAD 信息并继续。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-06
      • 1970-01-01
      • 2014-08-27
      • 1970-01-01
      相关资源
      最近更新 更多