文件上传踩坑记及文件清理原理探究

本文讲述了在实现文件异步上传时遇到的文件找不到异常问题,原因是请求线程结束时,上传的临时文件被清理。分析了Spring和Tomcat的文件清理原理,提出将文件存储步骤改为同步处理的解决方案,并提醒在try...finally中处理文件以避免异常导致的丢失。
摘要由CSDN通过智能技术生成

目录

  • 1. 糟糕的异步存储文件实现
  • 2. 异常原因推理
  • 3. 问题解决方式
  • 4. spring清理文件原理
  • 5. tomcat清理文件原理

 

最近搞一个文件上传功能,由于文件太大,或者说其中包含了比较多的内容,需要大量逻辑处理。为了优化用户体验,自然想到使用异步来做这件事。也就是说,用户上传完文件后,我就开启另一个线程来处理具体逻辑,主线程就直接返回用户成功信息了。这样就显得非常快了,要看具体结果可以到结果页进行查看。看起来很棒!

然后,我踩坑了。表象就是系统报找不到文件的错误。具体如下!

1.糟糕的异步存储文件实现

为快速起见,我将原来同步的事情,直接改为了异步。如下:

@RestController
@RequestMapping("/hello")
@Slf4j
public class HelloController {

    @PostMapping("uploadFileWithParam")
    public Object uploadFileWithParam(HttpServletRequest request,
                                      @RequestParam Map<String, Object> params) {
        log.info("param:{}", params);
        DefaultMultipartHttpServletRequest multipartRequest
                = (DefaultMultipartHttpServletRequest) request;
        MultipartFile file = multipartRequest.getFile("file");
        // 原本同步的工作,使用异步完成
        new Thread(() -> {
            // do sth else
            SleepUtil.sleepMillis(10L);
            if(file == null || file.isEmpty()) {
                log.error("文件为空");
                return;
            }
            try {
                file.transferTo(new File("/tmp/" + System.currentTimeMillis() + ".dest"));
            }
            catch (IOException e) {
                log.error("文件存储异常", e);
            }
            log.info("文件处理完成");
            // do sth else
        }).start();
        return "success";
    }
}

看起来挺简单的,实则埋下一大坑。也不是自己不清楚这事,只是一时糊涂,就干了。这会有什么问题?

至少我在本地debug的时候,没有问题。然后似乎,如果不去注意上传后的结果,好像一切看起来都很美好。然而,线上预期就很骨感了。上传处理失败,十之八九。

所以,结果就是,处理得快,出错得也快。尴尬不!具体原因,下节详述。

2.异常原因推理

为什么会出现异常?而且我们仔细看其异常信息,就会发现,其报的是文件未找到的异常。

实际也很简单,因为我们是开的异步线程去处理文件的,那么和外部的请求线程不是一起的。而当外部线程处理完业务后,其携带的文件就会被删除。

为什么会被删除呢?我还持有其引用啊,它不应该删除的啊。这么想也不会有问题,因为GC时只会清理无用对象。没错,MultipartFile 这个实例我们仍然是持有有效引用的,不会被GC掉。但是,其中含有的文件,则不在GC的管理范畴了。它并不会因为你还持有file这个对象的引用,而不会将文件删除。至少想做这一点是很难的。

所以,总结:请求线程结束后,上传的临时文件会被清理掉。而如果文件处理线程在文件被删除掉之后,再进行处理的话,自然就会报文件找不到的异常了。

同时,也可以解释,为什么我们在debug的时候,没有报错了。因为,这是巧合啊。我们在debug时,也许刚好遇到子线程先处理文件,然后外部线程才退出。so, 你赢了。

另有一问题:为什么请求线程会将文件删除呢?回答这个问题,我们可以从反面问一下,如果请求线程不清理文件,会怎么样呢?答案是,系统上可能存在的临时文件会越来越多,从而将磁盘搞垮,而这不是一个完美的框架该有的表现。

好了,理解了可能是框架层面做掉了清理这一动作,那么到底是谁干了这事?又是如何干成的呢?我们稍后再讲。附模拟请求curl命令:

    curl -F 'file=@uptest.txt' -F 'a=1' -F 'b=2' http://localhost:8081/hello/uploadFileWithParam

3.问题解决方式

ok, 找到了问题的原因,要解决起来就容易多了。既然异步处理有问题,那么就改成同步处理好了。如下改造:

@RestController
@RequestMapping("/hello")
@Slf4j
public class HelloController {

    @PostMapping("uploadFileWithParam")
    public Object uploadFileWithParam(HttpServletRequest request,
                                      @RequestParam Map<String, Object> params) {
       
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值