网盘项目遇到的问题总结

背景:客户需要一个网盘存放一些资料,让我们免费做一个系统。由于是免费的,我们直接找了一个开源项目简单修改了一些样式和配置后,稍微测试后就上线使用了,后面客户陆续反馈了一些问题

第一次反馈:文件传不上去

原因分析:上线前我们内部是测过的肯定能上传,除非遇到了特殊情况,再次沟通核实后掌握了实际情况:客户实际传输时是一个个文件夹的上传,每个文件夹几G 不等,文件夹里 有多层目录,最里面的单个文件 大小也可能达到G 级别

沟通后要到了客户的两个实际文件夹调试

测试发现的问题: 1)整个文件夹 上传时,有个请求是 GET 类型 ,但是请求URL 太长了 超出了浏览器限制。 尝试着分析前端代码 把 默认请求类型改成了 POST。测试后发现问题好了。 2)由于文件比较大,本地测试时 观察了 内存消耗 发现需要的内存比较大, 发现一个规律基本是 比 最大文件的 还大几百MB。和 运维同事 沟通后 把正式环境的内存改成了 4G JAVA_OPTS="-Xms4096m -Xmx4096m -Djava.security.egd=file:/dev/./urandom "

更新后又让客户去使用。

第二次反馈:传了几个文件夹成功了,但是某文件夹传着传着,进度条不动了

原因分析:检查日志发现了OOM, 看到OOM 第一时间 想到了 内存泄露,但是没有dump日志 也不好排查。接下来我做了两个工作

(1)修改启动参数使系统发现OOM时能自动生成 dump日志

JAVA_OPTS="-Xms4096m -Xmx4096m -Djava.security.egd=file:/dev/./urandom -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/hzjt-CloudDisk/Disk/heapdump.hprof"

这里需要注意的是文件要存到一个持久化的目录底下,防止docker重启后 文件丢失

加上参数后又让客户去使用,等待下次发生OOM

(2)在等待下次OOM期间,尝试通过一些插件 主动检查代码是否有BUG。

阿里代码规范插件能沟通帮助我们发现一些代码规范的错误和一些简单的代码错误,根据提示进行调整。

SonarLint 则能帮助我们发现一些隐藏BUG,比如有 空指针, IO没关闭 等

第三次反馈:文件传着传着,进度条不动了

原因分析: 检查日志 生成了 dump日志, 把 日志拿下来后 用 内存分析软件 分析, 我用的是 MAT ,当然也有其他的 JProfile等。 用MAT时遇到的问题, 目前新版本的MAT 必须是 JAVA11, 不能用JAVA8,另外dump日志比较大内存不够,通过修改ini文件 解决

用MAT打开日志文件后 发现 内存 都被一个叫_parts的 变量 占满, _parts变量 还不止一个,说明有多个大文件在上传。

检查代码发现 _parts 变量存放的 是 分片上传的 每个片的集合, 一个片 是 2MB, 也通过MAT发现了这个情况

如果文件比较大 _parts 的占用内存就比较大。

怎么解决这个问题呢,如果再增大内存 系统理论上也没问题,但是目前运维方面只能争取到4G 内存,本身项目是 不挣钱的,再 花钱 就太亏了, 最好是 通过修改代码 解决此问题。

最开始我想到的方案是能不能加锁控制,不能同时有两个大文件上传,这样内存方面 4G 基本也够用,这个思路 我尝试着修改了代码,但是测试发现 锁也只能 锁住同时 只能 上传一个片, 并不能 控制 锁一个大文件。另外发现加锁 后 文件上传的 速度 明显慢了, 这个方案基本就淘汰了。

再分析代码,为什么消耗这么大,是因为每片的文件在没合并之前都是驻留在内存里边,我们能不能先把片存到硬盘上呢,等到需要合并的时候再去硬盘读进来进行合并。根据这个思路尝试调整,另外在gitee里 找一下 大文件分片上传 的项目,看他们是怎么接收文件 合并文件的,最终对比觉得 FastLoader 这个项目 估计是 可行的,尝试修改代码。

每个片里不再存放文件流,新加了一个字段记录片的名字

fw.createAndSavePart(chunk, files.get(0).openStream());

Files.list(NIO2FileSystemVolume.fromTarget(finalParentDir.getTarget()))

.filter(path -> path.getFileName().toString().contains(fileName) && !path.getFileName().toString().equals(fileName))

.sorted((o1, o2) -> {

String p1 = o1.getFileName().toString();

String p2 = o2.getFileName().toString();

Matcher m1 = NUMBER_PATTERN.matcher(p1);

Matcher m2 = NUMBER_PATTERN.matcher(p2);

if (m1.find() && m2.find()) {

Integer i1 = Integer.parseInt(m1.group(2));

Integer i2 = Integer.parseInt(m2.group(2));

return i1.compareTo(i2);

} else {

return 0;

}

})

.forEach(path -> {

try {

logger.error(path.toString());

//以追加的形式写入文件

Files.write(NIO2FileSystemVolume.fromTarget(newFile.getTarget()), Files.readAllBytes(path), StandardOpenOption.APPEND);

//合并后删除该块

Files.delete(path);

} catch (IOException e) {

logger.error(e.getMessage(), e);

}

});

测试发现还真的好用,最让人意外的是合并文件时竟然也没占用多少内存,只是合并时耗时略长。

//以追加的形式写入文件

Files.write(NIO2FileSystemVolume.fromTarget(newFile.getTarget()), Files.readAllBytes(path), StandardOpenOption.APPEND);

至于为什么这个方法不占用内存我查了很多资料也没找到合适的说法,尝试跟了下源码也没看明白,或许还是对IO的理解不够。 单从效果上看 目前是 够用了。

最后又针对合并时间长做了一个异步操作,防止请求耗时太长造成 Ngnix 前端错误。

这样修改基本完成,测试同时多个大文件上传没有问题,并用Arthas 监控内存消耗, 抓取dump 都正常。 更新最后这一版 交给客户使用。

用了几天传了100+G数据,暂时没反馈有问题

自测发现下载比较大的文件下载不下来

偶然的测试测文件下载,发现文件偏大后就下载不下来,报了一个错误说 stream已关闭之类的, 开始以为是 服务器ngnix的配置 影响的,结果本地开发环境测试也不行 只好查代码方面的原因。 百度 搜到的结果没什么价值,唯一一个有用的帖子是 stackoverflow里的, 但是好像是说 中间件的问题。 我检查了配置是 用的undertown,改成了 tomcat后,问题消失了,测了其他功能也没什么影响

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值