Disk日记2

乐云一
  • 开发日记
  • 开发日记
About 2116 wordsAbout 7 min

云盘开发日记2

本来很长一段时间不会更新云盘这个坑了,但是前不久逛Github的时候,发现了一个Vue的插件 vue-simple-uploader。 作用是传输文件的时候,有分片上传/下载、断点上传/下载这样的功能,不过是通过Vue前端实现的。 了解了一下,想到自己还有一个云盘坑没填,指目前云盘的上传、下载功能,没经过特殊化处理。 也就顺其自然的去了解断点续传、分片续传、秒传、并发上传等等这些云盘应用肯定会有的场景及功能。 emo

蓝图

所以重新画了新云盘的蓝图,并且对项目结构进行的重构,之前根据WEB应用+纯服务应用搭建的。 所以架构仅仅是Web>Service>Dao这样的流程,很不利于往后将这个云盘拓展出来,所以目前将项目改造成了 Start>Web>Core>Dao>Manager模块 并且想了想这些功能的蓝图:

新增表

之前的表结构为:

file.png

可以看到核心表是和userId冗余的,但是这是张文件信息表,所以当前非常不可取。 所以拆成了以下表: file_user:记录文件与用户的绑定关系,file_iduser_idfile_md5:记录文件的唯一标识MD5码 并且将文件存储的思路从一用户一文件一云盘,改成了:一用户-大文件夹[存储所有文件的目录] 所以当实现秒传的时候,甚至不需要再操作文件了,直接更新file_user表的关联信息就行。

秒传

秒传目前其实是已经实现了,当时开发的时候也想到了,如果服务端已经有了上传的文件。 那么就只需要在服务端内部进行拷贝处理了。 但是当前是基于文件的名字、大小去定位相同的文件的,在本次更新中就使用了文件的数据流MD5的形式当作文件的唯一标识。 获得文件MD5的手法:

image.png

那么有了MD5作为文件标识,我们只需要将此值去file_md5中查询,所有有值则说明服务器中已经有这个文件了,只需要在file_user表中新增本次上传用户和次文件的绑定关联即可。

分片

经过后续了解,如果我们只是用一对一连接传输文件,问题很多:

  1. 任何一方断开,不管文件传输结果,都完全失败
  2. 上传量大或多时,导致服务端占用高,线程堵塞。
  3. 大文件在IO流传输中非常不稳定,容易造成数据丢失、修改等问题
  4. ..... 但是,以上这些问题都是基于一个场景下:大文件、量大且多。 所以如果是小文件传输上传、下载,依然可以使用当前这套:直接通过IO流一遍传输,你永远想象不到IO流的快速emo

那么我们处理大文件,其实不管是分片还是断点都是一个原理。 但是在上传场景中,我们一定要选择分片,理由如下:

  1. 当用户A上传文件,分片100,上传分片第50个时,断开。
  2. 此时服务端中已经存储了50个该文件的分片,并且记录了这个文件的唯一标识等。
  3. 这时候用户B巧合的上传了该文件,服务端读取到这个文件的MD5,发现可以延续上次用户A的上传,继续上传。
  4. 此时上传从50%开始,大大的减少了IO使用,并减少了很大部分的上传时间。
  5. 并且有一个重要的是,分片在前端的处理时,是可以同时发起很多小文件请求,并发处理,这个断点无法实现的。

所以就选择了分片进行处理,至于如何做,也已经在之前的文章上提到过了。 高效上传下载文件-后端处理模式open in new windowCSDNopen in new window

断点

断点感觉只适合下载的时候,首先对于上传,一个云盘产品首先应该保证的是上传速度-安全。 断点没有加速上传的体现,但是它具有极其可视的传输文件进度的能力。 所以下载的时候,使用断点下载可以非常直观的查看当前文件的进度条。 但是如果在下载场景使用断点的话,压力就来到了客户端这边。 服务端只能做数据解析再封装,以及定位跳点续传。 而文件保存、重处理等等这些需要在客户端完成。 所以断点下载的功能的可用性,非常考验客户端:

  1. 不变,指文件夹,用户不变
  2. 文件处理不出错,指处理时系统不崩溃。
  3. 数据完整,指不丢失数据。

但是断点续传场景只是在一条线程上执行,属于一对于传输模式,所以有很多复杂的场景也可以不用考虑

服务改造

目前不管是上传、查询、下载、更新等等操作,云盘都是一个纯微服务项目。所以不管什么请求,都是通过Rpc去再请求调用的。 现在想想,当时这样设计不是一般的蠢emo。莫名其妙的增加了服务器的压力,并且还多了很多考虑不到的不稳定性。 所以现在,只要查询更新这样的没有IO流操作的功能点才走一个简单的RPC请求。 上传下载这样的直接由云盘和前端进行对接。 这样好处很多,当对我来说最有意义的是: @FeignClient客户端,服务之间数据传输时,复杂类型带文件的难处理。 虽然有办法,在对象中嵌入MultipartFile,但是又要重写FeignEncode,又要规定两端数据类型等等这些。 又不好维护,还不好说有没有问题。 所以这两东西直接对接真实减轻了压力😡

特殊场景

目前猜测有如下:

  1. 非常大文件时,分片数量如何控制?
  2. 并发发起分片时,如何保证后端一定接受并响应,失败请求的措施怎么考虑
  3. 分片传输时,当最后一个分片最先被处理保存,那么该分片线程处于阻塞状态,如果是一个还好,如果有多个这样的文件场景,该如何处理? 目前是将最后一个分片的线程进行阻塞挂起,等待服务端分片总数=分片总数时,将其放开
LockSupport.unpark(ServerCode.threadUpload.get(fileMD5Value));
LockSupport.park();
  1. 断点,文件夹中的文件被替换,但是这个文件的偏移值不会改变,功能则失败,如何保证断点下载的文件还是那个文件。
  2. 一个老问题,计算文件的内存大小基于哪个单位合适。
  3. ......

总结

如果只是做一个简单的分片上传、断点下载其实还挺简单的。 不说代码,开源社区上已经有了大批的傻瓜式工具处理,并且这两种模式是文件传输老生常谈的功能了。 但是要高效,体验好,还需要很多细节判断。 比如什么情况下用分片,什么情况用普通,什么情况用断点。 虽然都知道大文件、小文件这概念,但是多小是小文件,多大是大文件是真不好说,一切都是基于服务器计算吧。 并且分片这一涉及并发的场景,在实际生产中肯定会出现一些意想不到的Bug的,还需要好好参谋emo

Last update:
Contributors: leyunone
Comments
  • Latest
  • Oldest
  • Hottest
Powered by Waline v2.14.7