最近在写一个 web 端的私人网盘服务,测试发现上传 2G 以上大文件时 后台会出现异常,请问有大佬做过相关的需求吗?怎么解决这类问题?
异常日志如下
ERROR 19593 --- [io-18073-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request; nested exception is java.io.IOException: org.apache.tomcat.util.http.fileupload.impl.IOFileUploadException: Processing of multipart/form-data request failed. java.io.EOFException] with root cause
java.io.EOFException: null ...
我修改了好多参数也不好使
@Configuration
@Slf4j
public class EmbeddedTomcatConfig implements WebServerFactoryCustomizer
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
log.info("Init EmbeddedTomcatConfig...");
((TomcatServletWebServerFactory)factory).addConnectorCustomizers(new TomcatConnectorCustomizer() {
@Override
public void customize(Connector connector) {
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
protocol.setMaxConnections(3000);
protocol.setMaxThreads(800);
protocol.setAcceptCount(200);
protocol.setSelectorTimeout(30000);
protocol.setSessionTimeout(60000 * 2);
protocol.setConnectionTimeout(60000 * 5);
protocol.setDisableUploadTimeout(false);
protocol.setConnectionUploadTimeout(60000 * 10);
}
});
}
}
application 参数
spring.servlet.multipart.max-request-size=-1 spring.servlet.multipart.max-file-size=-1 server.tomcat.max-swallow-size=-1 server.tomcat.max-http-form-post-size=-1
控制层
@ResponseBody
@ApiOperation(value = "上传文件",notes = "上传文件")
@RequestMapping(value = "/FilesUpload",method = RequestMethod.POST)
public BaseResponse uploadFiles(
@RequestParam(required = true) MultipartFile files,
HttpServletRequest request,
HttpServletResponse response
) {
if (files.isEmpty() || files.getSize() == 0) {
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
return BaseResponse.initErrorBaseResponse("不能上传空文件!");
}
try {
return BaseResponse.initSuccessBaseResponse(fileExecuteService.uploadFiles(files,request), "操作成功");
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
return BaseResponse.initErrorBaseResponse(e.getMessage());
}
}
1
xiaohundun 2022-12-09 13:24:57 +08:00
应该不是文件大小配置的问题,因为不是这个异常,这个异常应该看看是不是网络的问题
|
2
zsj1029 2022-12-09 13:41:02 +08:00 1
大文件请用流式传输,普通文件操作 2g 会占用 2g 物理内存,大了会炸
|
3
Stendan 2022-12-09 13:55:22 +08:00
|
4
koloonps 2022-12-09 14:01:45 +08:00
不用流式传输就需要在客户端把文件切好分开上传,不然分分钟 OOM
|
5
shiyu6226 OP @xiaohundun 这个是用在内网环境下的,网络应该是不受影响
|
7
shiyu6226 OP @zsj1029
@koloonps 实现方式是用的缓冲流写入的,实际运行过程中,内存使用一直保持在 500MB 上下,代码如下 private static boolean writeFileToLocal(String toLocalFilePath, MultipartFile file) throws Exception { boolean flag = false; BufferedOutputStream bufferedOutputStream = null; BufferedInputStream bufferedInputStream = null; try { bufferedInputStream = new BufferedInputStream(file.getInputStream()); bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(toLocalFilePath)); int index; byte[] bytes = new byte[4096]; while ((index = bufferedInputStream.read(bytes)) != -1) { bufferedOutputStream.write(bytes, 0, index); bufferedOutputStream.flush(); } flag = true; } catch (IOException e) { log.error("文件写入失败," + e.getMessage()); if (new File(toLocalFilePath).exists()) { new File(toLocalFilePath).delete(); } throw new Exception(e.getMessage()); } finally { if (bufferedOutputStream != null) { try { bufferedOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (bufferedInputStream != null) { try { bufferedInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } System.gc(); } |
9
Xhack 2022-12-09 15:52:18 +08:00
try-with-resource
|
10
zsj1029 2022-12-09 16:44:59 +08:00
@shiyu6226
``` @RequestMapping( value = "url", method = RequestMethod.POST ) public void uploadFile( @RequestParam("file") MultipartFile file ) throws IOException { InputStream input = upfile.getInputStream(); Path path = Paths.get(path);//check path OutputStream output = Files.newOutputStream(path); IOUtils.copy(in, out); //org.apache.commons.io.IOUtils or you can create IOUtils.copy } ``` You should close your stream after whole data is written. |
11
zsj1029 2022-12-09 16:55:37 +08:00 1
上面的简易文件流处理
另外 EOFException 的问题: 从文件中读取对象的时候,如何判断是否读取完毕。jvm 会给抛出 EOFException ,表示的是,文件中对象读取完毕。所以呢,你在判断是否读取结束的时候,捕获掉这个异常就可以,是捕获不是抛出。 重要的说三次,是捕获,捕获,捕获! |
12
DinnyXu 2022-12-09 17:27:52 +08:00
几年开发了?控制层的代码写成这样?哈哈
|
13
V2Axiu 2022-12-09 17:35:43 +08:00
大文件还是分片吧。还能续传多好
|
16
aguesuka 2022-12-09 20:09:42 +08:00
从你发的代码看不出啥问题.
首先得把问题定位到行, 你发的错误中只有信息而没有异常栈, 因为你的 controller catch 到异常没有打印日志(注意要打印异常栈). 打印异常栈后, 再判断是不是在你的 service 中报的错. 如果是在 service 中, 那直接把 service 改成 ``` private static void writeFileToLocal(String toLocalFilePath, MultipartFile file) throws IOException { try(InputStream inputStream = file.getInputStream()){ Files.copy(inputStream, Path.of(toLocalFilePath), StandardCopyOption.REPLACE_EXISTING); } } ``` 不要直接使用 inputstream, 不要吞异常, 不要 gc; |
17
aguesuka 2022-12-09 20:20:07 +08:00
对了打印日志的正确姿势是 log.error(e.getMessage(), e);这样才会打印异常栈
|
19
bertieranO0o 2022-12-09 20:56:15 +08:00
@DinnyXu 老哥求指点这个哪里有问题😂
|
20
DinnyXu 2022-12-09 22:05:19 +08:00
@eatFruit
@shiyu6226 @bertieranO0o 不是代码的问题,是这种上传文件还是很老式的写法,现在很多上传文件或者图片都是使用 OSS 搭配使用,2G 的文件压缩一下上传到云端是很快的,而且还可以进行分片,将 2G 文件压缩后分成 N 个子压缩包进行断点续传,最终传到服务端的是一个或多个 URL ,后端再进行异步处理。 |
21
DinnyXu 2022-12-09 22:09:45 +08:00
说一个大概的思路,前端检测上传的文件大小,根据文件大小进行压缩分片,比如 1G 的文件,分成 1024 个压缩包,每个压缩包 1M ,然后进行轮询请求上传到 OSS ,将 1024 个文件放到一个文件夹, 获取 OSS 的文件夹路径传给后端,由后端根据此路径读取 OSS 上传的所有分片文件,然后进行异步处理组装。
|
22
shiyu6226 OP @aguesuka
异常不在 service 也不在 controller ,我观察到的情况是 tomcat 缓存目录正在接收大文件时 到 2 个多 G 就中断 出异常了。 堆栈日志其实是打印全的,但是有点多,我就只发了主要的 |
25
aguesuka 2022-12-10 00:55:01 +08:00
spring.servlet.multipart.max-file-size: -1
spring.servlet.multipart.max-request-size: -1 亲测 ok, 依赖只有 spring-boot-starter-web:3.0.0 |
26
aguesuka 2022-12-10 02:00:27 +08:00
multipart 文件在转成 MultipartFile 的时候必须读完整个流, 所以会缓存到内存或硬盘里, 估计还有别的配置到上限了.
|
27
bertieranO0o 2022-12-10 02:28:45 +08:00
@aguesuka 一般不建议文件大小设置无限大,给个业务范围内的合理上限值即可
|
28
bertieranO0o 2022-12-10 02:31:19 +08:00
@DinnyXu 根本不需要这么复杂,就拿你举例的 OSS 来说,记得 19 年的时候 OSS 都已经有很成熟的支持分片上传的 API 了
|
29
DinnyXu 2022-12-10 15:28:29 +08:00
@bertieranO0o 你也知道你调用的是 API 哈,我说的是逻辑思维,API 谁不会调
|
30
bertieranO0o 2022-12-10 18:18:38 +08:00
@DinnyXu 想听听你的“异步处理组装”的逻辑
|