Spring Boot jar包部署文件上传报错:The temporary upload location [/tmp/tomcat.8389332062809917525 is not valid...

问题描述

原来的一个功能,一直都是可用的,结果今天试了一下,居然报错了。

我们用的是 Spring Cloud 那一套,本来应该转发到后台的业务请求没有接收到,在网关 zuul 哪里就报错了。

找了半天才知道是 zuul 网关报错,我一直以为是后台业务抛出的一异常,抛出异常也是有讲究的,异常抛出去了,得明明白白得知道是谁抛出得异常。

这样才能够方便问题排查。

报错内容大概是这样的:

java.io.IOException: The temporary upload location [/tmp/tomcat.8389332062809917525.8000/work/Tomcat/localhost/ROOT] is not valid
        at org.apache.catalina.connector.Request.parseParts(Request.java:2859) ~[tomcat-embed-core-8.5.32.jar!/:8.5.32]
        at org.apache.catalina.connector.Request.parseParameters(Request.java:3232) ~[tomcat-embed-core-8.5.32.jar!/:8.5.32]
        at org.apache.catalina.connector.Request.getParameter(Request.java:1137) ~[tomcat-embed-core-8.5.32.jar!/:8.5.32]
        at org.apache.catalina.connector.RequestFacade.getParameter(RequestFacade.java:381) ~[tomcat-embed-core-8.5.32.jar!/:8.5.32]
        at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:84) ~[spring-web-5.0.8.RELEASE.jar!/:5.0.8.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.0.8.RELEASE.jar!/:5.0.8.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.32.jar!/:8.5.32]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.32.jar!/:8.5.32]
        at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:155) ~[spring-boot-actuator-2.0.4.RELEASE.jar!/:2.0.4.RELEASE]
        at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:123) ~[spring-boot-actuator-2.0.4.RELEASE.jar!/:2.0.4.RELEASE]
        at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:108) ~[spring-boot-actuator-2.0.4.RELEASE.jar!/:2.0.4.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.0.8.RELEASE.jar!/:5.0.8.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.32.jar!/:8.5.32]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.32.jar!/:8.5.32]
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.0.8.RELEASE.jar!/:5.0.8.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.0.8.RELEASE.jar!/:5.0.8.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.32.jar!/:8.5.32]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.32.jar!/:8.5.32]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) ~[tomcat-embed-core-8.5.32.jar!/:8.5.32]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-8.5.32.jar!/:8.5.32]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493) [tomcat-embed-core-8.5.32.jar!/:8.5.32]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.32.jar!/:8.5.32]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) [tomcat-embed-core-8.5.32.jar!/:8.5.32]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.32.jar!/:8.5.32]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) [tomcat-embed-core-8.5.32.jar!/:8.5.32]

定位原因也很简单,就是因为 Spring Boot jar 包部署会在 /tmp 目录下创建临时目录,存放一些上传的数据,然后这个临时目录过了不知道多少天,就被删除了。

导致后面再去用这个临时目录的时候发现没有了,然后就报错了。

在linux系统中,springboot应用服务再启动(java -jar 命令启动服务)的时候,会在操作系统的 /tmp 目录下生成一个tomcat*的文件目录,上传的文件先要转换成临时文件保存在这个文件夹下面。

由于临时/tmp目录下的文件,在长时间(10天)没有使用的情况下,就会被系统机制自动删除掉。所以如果系统长时间没有使用到临时文件夹,就可能导致上面这个问题。

有几个问题值得我们思考:

  • tmp 目录?
  • Spring Boot jar 包部署时的临时目录是怎么来的?
  • Spring Boot jar包的临时目录用来干什么的?
  • 分布式应用下的临时目录需要改进的地方?

tmp 目录

Linux 系统的根文件夹下有一个 tmp 目录,从字面上我们都知道它是一个存放临时文件的目录。

除此之外我们还知道什么呢?啥也不知道吧,这里来学习一下。

tmp 目录是啥?

 

tmp 清理规则

为了保证tmp目录不爆满,系统默认情况下每日会处理一次tmp目录文件,原理就是使用了 tmpwatch。

tmpwatch作用:removes files which haven’t been accessed for a period of time,删除指定的目录中一段时间未访问的文件。

tmp 目录的清理规则以 centos 为例,是在 /usr/lib/tmpfiles.d/tmp.conf 中,比如:

Spring Boot jar包部署文件上传报错:The temporary upload location [/tmp/tomcat.8389332062809917525 is not valid...

看到了吧,上面的配置表示一般时 10 天清理一次。

他喵的,为啥我在机器上没看到 tmpwatch 命令,那这个脚本又是被谁执行的呢?

pass

Spring Boot jar 包部署时的临时目录是怎么来的?

在springboot项目启动后,系统会在 "/tmp" 目录下自动的创建几个目录

  • tomcat.************.8080,(结尾是项目的端口)
  • tomcat-docbase.*********.8080。

对于Multipart(form-data)的方式处理请求时,默认就是在第二个目录下创建临时文件的。

如何自定义这两个目录:

tomcat.************.8080,(结尾是项目的端后)

这个临时文件是TOMCAT启动的日志路径目录,需要在application.properties 下配置:

server.tomcat.basedir=/XXXXXX/tomcat/

tomcat-docbase.*********.8080

对于这个临时文件是请求上传文件是的临时目录文件,配置如下:

1.修改tomcat启动配置

添加-Djava.io.tmpdir=xxxx

2.添加配置

@Configuration
public class TomcatConfig {
    @Autowired
    private Environment environment;

    /**
     * 文件上传临时路径
     */
    @Bean
    MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        factory.setLocation(environment.getProperty("server.tomcat.basedir"));
        return factory.createMultipartConfig();
    }
}

 

From:https://blog.csdn.net/qq_25406669/article/details/86472572

解决方案:

1.重启服务,临时方案:会重新生成tomcat目录,但是生产环境不建议如此操作;

2.1增加服务配置,自定义baseDir:

2.2启动时增加参数-Djava.io.tmpdir=自定义目录

3.修改tmpwatch 删除文件的逻辑,系统级别的命令,不建议操作

4.在网上看到有:编码的方式catch异常,生成删除的文件夹;(方法未验证)

在网上还查阅到较好的解决办法:

注入一个Bean,手动配置临时目录

 

Spring Boot jar包的临时目录用来干什么的?

存临时目录的吧,我就不深究了

分布式应用下的临时目录需要改进的地方?

 不可能每个项目都去设置这种临时目录,原因是不容易扩展,每次都要注意这种问题,而且还要地方临时目录太大,吃满了磁盘空间。

好的方案应该是先上传到诸如 OSS 这种网络存储系统,然后把 url 发送给后台。

这种的好处是每个应用不用管文件上传这块,易于扩展。