先来说下这个分布式上传功能的背景
来公司之后很长一段时间都在优化改造审核系统,现在审核系统稳定了,而且也可以快速接需求了。这时候被借到学院这边来做一些优化改造,其中视频分布式上传这个功能存在很多问题,急需改造处理
学院视频上传功能简介
学院这边上传视频的大致步骤如下
- 老师在教师管理后台上传视频
- 教师管理后台再把视频上传到阿里云的点播平台
- 审核人员去审核上传的视频资源
- 将审核通过的视频进行转码处理
- 将转码后生成的视频加密的播放地址绑定到课时当中
- 学院通过加密的播放地址播放课程进行学习
因此上传视频的功能是非常重要的一环,它的服务对象是老师,在未改造之前的上传功能存在如下问题:
1. 由于历史原因,老的上传功能是 php 实现的、而现有的团队都是 java 程序员,这就导致出现问题无法进行维护
2. 老师上传的文件会出现花屏、丢帧等一些质量问题, 导致学院学生观看视频的时候客诉率很高
3. 老师上传的文件的原文件播放时长与学生看到播放时长存在不一致情况
4. 老师在管理后台上传的视频是成功状态,但是后端实际存储的状态是未上传成功的状态
5. 老师上传视频完成之后无法覆盖之前的视频
所以改造视频上传这块的功能就成了一个迫在眉睫的一个事情
改造上传功能的限制条件
- 前端限制条件
因为项目的历史背景原因很重,前端也缺乏人员支持,前期我们的打算是,前端代码尽量不动,只优化后端,等前端那边人员支持充足,再进一步优化,下面是先有前端进行分片上传的过程
前端会把一个大的视频文件,切分成一小片一小片的,然后挨个调用后端接口,将分片数据上传上去,如上图,假设我有一个测试视频文件,大小是 43760633 个字节,我把它拆分成每个分片为 5000000 个字节的字节块,总共会切分成 9块,先从块1开始上传,块1上传完成成功之后,再接着上传块2,直到最后一个块上传完成。
注: 这里的前端有个重要的约束条件,就是分片上传是一块一块上传的,且有先后顺序
- 后端限制条件
后端点播是采购的阿里云,因此得用阿里云的sdk 将视频上传到阿里云的点播平台
阿里云sdk 提供如下一些上传方法
- 提供本地视频文件
- 提供本地文件流或网络流
- 提供视频网络地址
下面是阿里云官网的上传例子
// 视频文件上传
// 视频标题(必选)
String title = "测试标题";
// 1.本地文件上传和文件流上传时,文件名称为上传文件绝对路径,如:/User/sample/文件名称.mp4 (必选)
// 2.网络流上传时,文件名称为源文件名,如文件名称.mp4(必选)。
// 3.流式上传时,文件名称为源文件名,如文件名称.mp4(必选)。
// 任何上传方式文件名必须包含扩展名
String fileName = "/Users/test/video/test.mp4";
// 本地文件上传
testUploadVideo(accessKeyId, accessKeySecret, title, fileName);
// 待上传视频的网络流地址
String url = "http://test.aliyun.com/video/test.mp4";
// 2.网络流上传
// 文件扩展名,当url中不包含扩展名时,需要设置该参数
String fileExtension = "mp4";
testUploadURLStream(accessKeyId, accessKeySecret, title, url, fileExtension);
// 3.文件流上传
testUploadFileStream(accessKeyId, accessKeySecret, title, fileName);
// 4.流式上传,如文件流和网络流
InputStream inputStream = null;
// 4.1 文件流
try {
inputStream = new FileInputStream(fileName);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 4.2 网络流
try {
inputStream = new URL(url).openStream();
} catch (IOException e) {
e.printStackTrace();
}
testUploadStream(accessKeyId, accessKeySecret, title, fileName, inputStream);
阿里云提供的 sdk 必需满足一个条件,就是上传的文件或者视频它是本地完整的文件或者流
这里是sdk使用方法:https://help.aliyun.com/document_detail/53406.html
在改造这个上传功能之前的几种实现方案
方案一,通过代理服务器,将同一个课时的视频定向传输到一台服务器
举个例子:
- 前端把视频文件分片上传到后端服务器
- nginx 会反向代理这个上传请求(我们可以在nginx 上面配置一些定向规则),比如我这里的视频对应的课时 id 为 589776 经过gninx 定向规则,定向到了服务 1 上面
- 因为每个分片数据上传的时候都会带上课时 id,所以所有的分片数据最终都会存储在服务 1上面,我们在服务1上面进行分片数据合并,然后发送到阿里云点播平台即可
- 这个方案的缺点:
1. 需要额外的反向代理服务配置
2. 无法进行动态扩容缩容,尤其不适用容器的部署方式
3. 每台服务器都需要分配足够的磁盘空间大小,需要更多的磁盘资源
4. 很容器出现服务器倾斜的问题
5. 业务处理服务与上传存储服务耦合
1. 业务处理需要把上传文件的中间态保存下来
2. 业务端上传成功,实际并未真正上传成功
6. 需要保留很多文件中间状态,代码实现很繁杂
7. 上传失败之后无法提前告知用户
- 优点
实现起来很直接,也很容易想到这种方案
因为方案 1缺点太多了, 我们就自然而然想到下面的方案二,它能解决方案 一中的大部分问题
方案二,每台服务服务都挂载一个共享磁盘
方案二的实现方式,比较简单,就是每个服务挂载一个共享磁盘,所有上传的分片文件都存储到这个共享磁盘当中,当所有分片上传完成之后,组合共享文件磁盘当中的分片为一个完整的文件,再上传到阿里云的点播平台
方案二解决了方案一中 1、2、3、4 点的问题
- 但方案二也有如下缺点
1. 需要配置额外的共享磁盘
2. 无论业务方分片上传,还是将文件发送到点播云,都需要经过从共享磁盘到业务服务器之间的网络传输,上传性能更差,更多的io意味着更多的不稳定性
3. 业务处理服务与上传存储服务耦合
1. 业务处理需要把上传文件的中间态保存下来
2. 业务端上传成功,实际并未真正上传成功
4. 也需要保留很多文件的中间状态,代码实现也会相对复杂
5. 上传失败之后无法提前告知用户
- 它的优点
实现