问题

项目解决的问题主要是java实现分片上传功能,问题描述:
楼主在公司最近项目中使用multipart文件上传视频文件到服务器上,然后用fastdfs保存到数据库中。发现当上传的视频文件太大的时候会使服务器内存的buf/cache占用很高(好几个G),虽然可以手动清除,但是依旧无法从根源上解决视频上传内存占用太大的问题。


清理的方式如下,可以看到清理前后buff/cahce有明显变小

sync;echo 1 > /proc/sys/vm/drop_caches # 表示清除pagecache。
sync;echo 2 > /proc/sys/vm/drop_caches # 表示清除回收slab分配器中的对象(包括目录项缓存和inode缓存)。slab分配器是内核中管理内存的一种机制,其中很多缓存数据实现都是用的pagecache。
sync;echo 3 > /proc/sys/vm/drop_caches # 表示清除pagecache和slab分配器中的缓存对象。


解决问题的思路

lz花了100积分在csdn上面提的问题:急急急,求java上传大文件占用jvm过高的问题解决方案/思路

1、mmf,通过memory mapped file 内存映射文件将数据分段存储到mysql或者其他数据库中,不适合,略
2、服务器上ftp,然后通过代码来借助ftp实现
3、前端通过vue-upload来实现文件的分片上传功能,后端使用fastdfs自带的分片功能实现数据存储(lz的解决方式)
4、使用ffmpeg将视频分成m3u8视频然后本地服务器再实现合成


解决的问题/实现的功能

前端实现的功能:simple-uploader.js(也称 Uploader) 是一个上传库,支持多并发上传,文件夹、拖拽、可暂停继续、秒传、分块上传、出错自动重传、手工重传、进度、剩余时间、上传速度等特性;该上传库依赖 HTML5 File API。

后端实现的功能:springboot 整合 fastDfs,redis实现,.本地路径文件分片上传 2.fastDfs 文件上传,下载,分片上传


项目实现技术 - 先说一下使用到大概的东西,防止大家数据库或者其他原因无法使用,导致浪费时间。 **使用到的技术:simple-uploader(前端) + fastdfs(数据库)+ springboot(项目框架)+Redis**

项目源地址:

  • 前端: simple-uploader
  • vue前端:vue -simple-uploader
  • 后端: fastDfs-demo
    注:十分感谢提供开源项目的作者,楼主的代码是根据两个项目改变来的,这两个项目是源码。
  • 楼主自己的代码:
    链接:地址
    提取码:ey36

实现的效果 -



实现原理:

  • 前端使用分片插件后,一个请求会被分成多个请求。多个upload请求均为分片的请求,把大文件分成多个小份一次一次向服务器传递分片完成后,即upload完成后,需要向服务器传递一个merge请求,让服务器将多个分片文件合成一个文件,当我们上传一个大文件时,会被插件分片,ajax请求如下:

  • 可以看到发起了多次upload的请求,我们来看看upload发送的具体参数

  • 第一个配置(content-disposition)中的guid和第二个配置中的access_token,是我们通过webuploader配置里的formData,即传递给服务器的参数后面几个配置是文件内容,chunkNumber、chunkSize、currentChunkSize等其中totalChunks为总分片数,chunkSize为当前第几个分片。图片中为13。当你看到chunk是130的upload请求时,代表这是最后一个upload请求了

  • 分片后,文件还未整合,数据大概是下面这个样子:

  • 后台的验证
    1 在“加入文件”的回调中,通过FileReader读取文件,生成MD5,发给后台
    2.1 如果后台直接返回了“跳过上传”字段和文件的url,则跳过上传,这是秒传;
    2.2 如果后台返回了分片信息,这是断点续传。后台会在每个分片中标识这个分片是否上传过,你需要在分片上传校验的回调中判断,如果true则跳过该分片。
    3 每个分片上传成功,后台都会返回一个字段判断是否需要合并;在“上传完成”的回调中,如果这个字段为true,则需要给后台发一个请求合并的ajax请求

代码分享

楼主前面分享的项目改后的代码

后端核心代码:

api层:

@PostMapping(value = "/fastDfsChunkUpload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)public Map chunkUpload1(MultipartFileParam multipartFileParam, HttpServletResponse response) {Map<String, String> map = new HashMap<>();long chunk = multipartFileParam.getChunkNumber();long totalChunk = multipartFileParam.getTotalChunks();long chunkSize = multipartFileParam.getChunkSize();long historyUpload = (chunk - 1) * chunkSize;String md5 = multipartFileParam.getIdentifier();MultipartFile file = multipartFileParam.getFile();String fileName = FileUtil.extName(file.getOriginalFilename());StorePath path = null;String groundPath;try {if (chunk == 1) {path = appendFileStorageClient.uploadAppenderFile(UpLoadConstant.DEFAULT_GROUP, file.getInputStream(),file.getSize(), fileName);if (path == null) {map.put("result", "上传第一个就错了");response.setStatus(500);return map;} else {redisUtil.setObject(UpLoadConstant.uploadChunkNum + md5, 1, cacheTime);map.put("result", "上传成功");}groundPath = path.getPath();redisUtil.setObject(UpLoadConstant.fastDfsPath + md5, groundPath, cacheTime);} else {groundPath = (String) redisUtil.getObject(UpLoadConstant.fastDfsPath + md5);appendFileStorageClient.modifyFile(UpLoadConstant.DEFAULT_GROUP, groundPath, file.getInputStream(),file.getSize(), historyUpload);Integer chunkNum = (Integer) redisUtil.getObject(UpLoadConstant.uploadChunkNum + md5);chunkNum = chunkNum + 1;redisUtil.setObject(UpLoadConstant.uploadChunkNum + md5, chunkNum, cacheTime);}Integer num = (Integer) redisUtil.getObject(UpLoadConstant.uploadChunkNum + md5);if (totalChunk == num) {response.setStatus(200);map.put("result", "上传成功");map.put("path", groundPath);redisUtil.del(UpLoadConstant.uploadChunkNum + md5);redisUtil.del(UpLoadConstant.fastDfsPath + md5);}} catch (FdfsIOException | SocketTimeoutException e) {response.setStatus(407);map.put("result", "重新发送");return map;} catch (Exception e) {e.printStackTrace();redisUtil.del(UpLoadConstant.uploadChunkNum + md5);redisUtil.del(UpLoadConstant.fastDfsPath + md5);response.setStatus(500);map.put("result", "upload error");return map;}System.out.println("result=" + map.get("result"));System.out.println("path=" + map.get("path"));return map;}

实体类:MultipartFileParam

package com.dgut.fastdfs.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.web.multipart.MultipartFile;import java.io.Serializable;/*** @author :CZW* @date :Created in 2019/12/15 12:27* @description:*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class MultipartFileParam implements Serializable {private String taskId;//文件传输任务IDprivate long chunkNumber;//当前为第几分片private long chunkSize;//每个分块的大小private long totalChunks;//分片总数private String identifier;//文件唯一标识private MultipartFile file;//分块文件传输对象}

工具类:RedisUtil

package com.dgut.fastdfs.utils;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import java.util.List;
import java.util.concurrent.TimeUnit;@Component
public class RedisUtil {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;//写入对象public boolean setObject(final String key, Object value, Integer expireTime) {try {redisTemplate.opsForValue().set(key, value);redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);return true;} catch (Exception e) {e.printStackTrace();return false;}}//获取对象public Object getObject(final String key) {return key == null ? null : redisTemplate.opsForValue().get(key);}//写入集合public boolean setList(final String key, Object value, Integer expireTime) {try {redisTemplate.opsForList().rightPush(key, value);redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);return true;} catch (Exception e) {e.printStackTrace();return false;}}//获取集合public List<Object> getList(final String key) {try {return redisTemplate.opsForList().range(key, 0, -1);} catch (Exception e) {e.printStackTrace();return null;}}//判断时候存在keypublic boolean hasKey(final String key) {try {return redisTemplate.hasKey(key);} catch (Exception e) {e.printStackTrace();}return false;}//删除keypublic void del(final String key) {if (hasKey(key)) {redisTemplate.delete(key);}}}

前端主要需要修改的地方

  • 访问的api
  • 同时上传的分片数量

    项目代码已经上传到百度网盘上面去了,可以自己去下载

尾声

lz花了两个小时的时间整理资源,忘大家看到后别忘了点个赞,谢谢


来生还长,切勿惆怅


*******************20210408网友提问补充******************

问题:fastdfs如何配置以及相关依赖

依赖(好像不是很好下载,可能需要去github上下载)

<!--FastDFS--><dependency><groupId>org.csource</groupId><artifactId>fastdfs-client-java</artifactId><version>1.27-SNAPSHOT</version></dependency><dependency><groupId>com.github.tobato</groupId><artifactId>fastdfs-client</artifactId><version>1.26.2</version></dependency>

配置文件项目位置及参数:fdfs_client.properties

connect_timeout = 10
network_timeout = 30
charset = UTF-8
http.tracker_http_port = 80
http.anti_steal_token = no
http.secret_key = FastDFS1234567890
tracker_server = FastDFS_IP:22122

加载FastDFS配置文件的工具类

import org.csource.common.MyException;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.URLDecoder;/*** @author Marsj* @description: FastDFS工具类【实现文件上传、下载、删除、查询】* @date 2019/3/26*/public class FastDFSClientUtil {private TrackerClient trackerClient;private TrackerServer trackerServer;private StorageServer storageServer;private StorageClient1 storageClient;public FastDFSClientUtil() throws IOException, MyException {//        FastDFSConfig config = ApplicationContext.getBean(FastDFSConfig.class);//使用类加载器方法加载配置文件在linux上打成jar包后会造成无法读取参数,可以使用上面方法或者直接写服务器配置文件路径String osName = System.getProperty("os.name");if (osName.startsWith("Windows")) {// windowsString conf = this.getClass().getClassLoader().getResource("fdfs_client.properties").getPath();String path = URLDecoder.decode(getClass().getProtectionDomain().getCodeSource().getLocation().toString(), "UTF-8");path = path.substring(6);conf = conf.replace("classpath:", URLDecoder.decode(path, "UTF-8"));ClientGlobal.init(conf);} else {ClientGlobal.init("/data/fdfs_client.properties");}trackerClient = new TrackerClient();trackerServer = trackerClient.getConnection();storageServer = null;storageClient = new StorageClient1(trackerServer, storageServer);}/*** @param filePath 需要查询的文件路径* @return 为null则代表文件不存在,正常返回FileInfo文件信息(包含文件大小,创建时间,上传者的id等等)* @author Czw* @Description 查询FastDFS中路径文件是否存在* @date 2019/5/8 下午 6:58*/public FileInfo query_file_info(String filePath) {FileInfo fileInfo = null;try {fileInfo = storageClient.query_file_info1(filePath);} catch (IOException | MyException e) {e.printStackTrace();}return fileInfo;}/*** 上传文件方法* <p>Title: uploadFile</p>* <p>Description: </p>** @param fileName 文件全路径* @param extName  文件扩展名,不包含(.)* @param metas    文件扩展信息*/public String uploadFile(String fileName, String extName, NameValuePair[] metas) {String result = null;try {result = storageClient.upload_file1(fileName, extName, metas);} catch (Exception e) {e.printStackTrace();}return result;}/*** 上传文件,传fileName** @param fileName 文件的磁盘路径名称 如:D:/image/aaa.jpg* @return null为失败*/public String uploadFile(String fileName) {return uploadFile(fileName, null, null);}/*** @param fileName 文件的磁盘路径名称 如:D:/image/aaa.jpg* @param extName  文件的扩展名 如 txt jpg等* @return null为失败*/public String uploadFile(String fileName, String extName) {return uploadFile(fileName, extName, null);}/*** 上传文件方法* <p>Title: uploadFile</p>* <p>Description: </p>** @param fileContent 文件的内容,字节数组* @param extName     文件扩展名* @param metas       文件扩展信息*/public String uploadFile(byte[] fileContent, String extName, NameValuePair[] metas) {String result = null;try {result = storageClient.upload_file1(fileContent, extName, metas);} catch (IOException | MyException e) {e.printStackTrace();}return result;}/*** 上传文件** @param fileContent 文件的字节数组* @return null为失败*/public String uploadFile(byte[] fileContent) {return uploadFile(fileContent, null, null);}/*** 上传文件** @param fileContent 文件的字节数组* @param extName     文件的扩展名 如 txt  jpg png 等* @return null为失败*/public String uploadFile(byte[] fileContent, String extName) {return uploadFile(fileContent, extName, null);}/*** 文件下载到磁盘** @param path   图片路径* @param output 输出流 中包含要输出到磁盘的路径* @return -1失败,0成功*/public int download_file(String path, BufferedOutputStream output) {int result = -1;try {byte[] b = storageClient.download_file1(path);try {if (b != null) {output.write(b);result = 0;}} catch (Exception e) {} //用户可能取消了下载finally {if (output != null) {try {output.close();} catch (IOException e) {e.printStackTrace();}}}} catch (Exception e) {e.printStackTrace();}return result;}/*** 获取文件数组** @param path 文件的路径 如group1/M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg* @return*/public byte[] download_bytes(String path) {byte[] b = null;try {b = storageClient.download_file1(path);} catch (Exception e) {e.printStackTrace();}return b;}/*** 删除文件** @param group       组名 如:group1* @param storagePath 不带组名的路径名称 如:M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg* @return -1失败,0成功*/public Integer delete_file(String group, String storagePath) {int result = -1;try {result = storageClient.delete_file(group, storagePath);} catch (Exception e) {e.printStackTrace();}return result;}/*** @param storagePath 文件的全部路径 如:group1/M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg* @return -1失败,0成功*/public Integer delete_file(String storagePath) {int result = -1;try {result = storageClient.delete_file1(storagePath);} catch (Exception e) {e.printStackTrace();}return result;}/*** 获取远程服务器文件资源信息** @param groupName      文件组名 如:group1* @param remoteFileName M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg*/public FileInfo getFile(String groupName, String remoteFileName) {try {return storageClient.get_file_info(groupName, remoteFileName);} catch (Exception e) {e.printStackTrace();}return null;}/*** 获取远程服务器文件资源信息** @param remoteFileName /group01/M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg*/public FileInfo getFile(String remoteFileName) {try {return storageClient.get_file_info1(remoteFileName);} catch (Exception e) {e.printStackTrace();}return null;}}

20210510网友提问补充**
上传小文件时正常,上传大文件时报错找不到节点,报错点是appendFileStorageClient.modifyFile 处的代码,个人认为是fastdfs的tracker_server 或者tracker_list参数配置错误导致的,目前还在找问题中,及时更新


************20210522补充******************* 上面提到的 节点找不到的错误经过网友@阿姆斯特狼 差不多一个月的努力总算找到了问题的原因,跟我之前的猜想差不多,确实是fastdfs的参数有问题,需要把store_group 和store lookup的参数配置正确就可以,特别是store lookup参数默认参数是1,指定. 0就是轮训

java实现大文件分片上传功能(前后端都有,代码down下来配置完后可以直接运行)相关推荐

  1. java实现大文件分片上传

    java实现大文件分片上传 在项目中用到了大文件上传功能,最初从网上参考了一些代码来实现,但是最终的上传效果不是很好,速度比较慢. 之前的上传思路是: 前端利用webUploader分片大文件 后端接 ...

  2. React大文件分片上传方案

    最近做了大文件(文件夹)分片上传的需求,记录一下. 原理: 前端进行大文件分片上传的方案几乎都是利用Blob.prototype.slice方法对文件进行分片,用数组将每一个分片存起来,最后将分片发给 ...

  3. minio实现大文件分片上传+断点续传+预览

    minio实现大文件分片上传+断点续传+预览 只提供后端java代码 思路: 前端分片 校验文件md5是否已经存在 --不存在创建临时桶存分片 校验分块是否已经上传 分块上传 合并分块 校验合成后md ...

  4. 大文件分片上传前后端实现

    最近在做公司的视频业务,涉及到大视频的上传. 之前的图片.Excel等上传做的很简单,直接表单提交后端用MultipartFile接收保存到磁盘就行了. 但是针对大文件的上传,需要做额外的处理,否则可 ...

  5. AWS-S3通用存储操作,操作minio、oss、cos等所有兼容s3协议的云存储(含有大文件分片上传实现)

    一.介绍 通用存储操作common包,支持所有兼容amazon-s3协议的云存储,如minio.oss.cos等,以后客户用啥云储存一套代码都能搞定了,真棒~ 二.代码结构 三.代码实现 3.1 po ...

  6. 大文件分片上传,断点续传,秒传 实现

    前段时间做视频上传业务,通过网页上传视频到服务器. 视频大小 小则几十M,大则 1G+,以一般的HTTP请求发送数据的方式的话,会遇到的问题:1,文件过大,超出服务端的请求大小限制:2,请求时间过长, ...

  7. jquery 分片上传php,php 大文件分片上传

    前端部分 上传 //上传控件 uploadBig('upload','zip,rar,7z,tar',{ id: '', type: 'upload_file', } ,(res)=>{ //t ...

  8. 大文件分片上传前端框架_基于Node.js的大文件分片上传

    基于Node.js的大文件分片上传 我们在做文件上传的时候,如果文件过大,可能会导致请求超时的情况.所以,在遇到需要对大文件进行上传的时候,就需要对文件进行分片上传的操作.同时如果文件过大,在网络不佳 ...

  9. Vue项目中遇到了大文件分片上传的问题

    Vue项目中遇到了大文件分片上传的问题,之前用过webuploader,索性就把Vue2.0与webuploader结合起来使用,封装了一个vue的上传组件,使用起来也比较舒爽. 上传就上传吧,为什么 ...

最新文章

  1. php foreach是什么,php中foreach的用法是什么
  2. 性能测试07:服务器性能测试范围
  3. ERP_Oracle Erp 11i 和 R12的区别概述(概念)
  4. Android多线程之同步锁的使用
  5. 【招聘(上海)】 坚果云 招聘Windows客户端(WPF方向)
  6. B cannot be cast to java.sql.Blob
  7. tf.Variable、tf.get_variable、tf.variable_scope以及tf.name_scope
  8. java swing jcheckbox_Swing JCheckBox类
  9. C语言基础教程之储存类
  10. Linux sed 批量替换多个文件中的字符串【转载】
  11. python如何使用多线程_python实现多线程教程
  12. TCP协议如何保证可靠传输
  13. 短视频是如何赚钱的?切入短视频风口的3种玩法
  14. sz命令获取文件到本地路径设置
  15. 数据结构与算法之美-问题与思考收集
  16. 关于 Eclipse 使用的自习笔记
  17. 在剧里跟“刘亦菲”学营销:地推撬动“社交播传”
  18. Python爬虫+简易词云的制作
  19. linux备份磁盘数据,linux下vmstat输出数据分析-linux下dd命令备份磁盘的节点(...-linux watch命令用法简介(图文)_169IT.COM...
  20. house of apple2(改进)

热门文章

  1. word中硬回车(enter)与软回车(shift+enter)
  2. 屠蛟之路_重伤的屠蛟俊_ThirdDay
  3. 电影推广思路详解,最权威的电影推广方案
  4. BurpSuite系列(十)----Extender模块(扩展器)
  5. 设计模式之禅【代理模式】
  6. 计算机的关闭程序,电脑中取消关机时强制关闭程序提醒的方法
  7. android 微信评论功能,Android仿微信朋友圈点击评论自动定位到相关行功能
  8. 电脑没有html怎么办,笔记本电脑里没有无线网络连接的选项怎么办
  9. 浏览器环境 兼容运行ES6语法
  10. vscode 实时运行代码插件 Quokka