首页 > 其他分享 >多文件并行上传方案设计

多文件并行上传方案设计

时间:2023-11-09 09:37:53浏览次数:31  
标签:方案设计 文件 formData 并行 NSString 分片 appendData 上传

多文件并行上传方案设计 https://mp.weixin.qq.com/s/Zb-PBejtSBLaBN0LEPrjVg

多文件并行上传方案设计

原创 刘壮 搜狐技术产品 2023-11-09 07:30 发表于北京

 

 

本文字数:2360

预计阅读时间:15分钟


01

背景

抖音、快手等短视频 APP 都有本地编辑视频并上传的功能,这里的上传指的就是上传视频文件,其实无论是上传视频还是其他文件,技术原理都是相同的。

搜狐视频 APP 的文件上传除了基础的上传功能外,还支持多个视频文件的上传处理,以串行的形式进行上传。并且,在单个视频文件的上传中,为了保证充分利用带宽,还设计了并行上传的逻辑。整体方案如下。

02

方案设计

1、任务管理

下图就是上传整体逻辑,上传逻辑分为三部分,核心上传逻辑由 UploadManager 实现,其他都是偏业务的:

图片

  1. 每个视频文件的上传,都会被当做一个 task 来处理。上传前会先判断是否秒传,后面会对秒传逻辑进行详细讲解;

  2. 所有任务都由 uploadTasks 进行管理,当添加新任务时,会判断是否有任务在上传中。如果没有任务进行上传,会先将任务加入到 uploadTasks 中,随后就会进入上传流程。如果有任务在上传中,则会将任务添加到 uploadTasks 后,在队列中等待前面的任务上传完成;

  3. 随后进入分片上传的逻辑,所有分片上传完成后,会自动开始下一个文件的上传。

需要注意的是,当视频删除时,需要调用接口告知服务端,这个文件 id 废弃,以及删除已上传文件。

2、秒传逻辑

由于视频文件比较大,比较占服务器存储空间,而且不同的 CDN 节点还会存在多个相同的备份,这样存储空间占用更严重。本质上来说,同一份文件在服务器只需要存在一份,所以对于这个问题,我们设计了一套排重逻辑。

在获取上传地址前,我们会计算一份视频文件的 md5,并在上传地址接口传给服务端,服务端会做比对。如果存在相同文件会直接走秒传逻辑,下发一个文件 id,客户端只通过文件 id 更新视频信息,不上传视频文件,这样从业务层就完成了上传流程。

3、分片上传

这里需要先分清 task 和分片的概念,每个文件对应一个 task,一个文件会被切为多个分片进行上传。上传方式是表单提交,具体流程如下图:

图片

  1. 上传地址是由服务器动态下发的,并不是固定地址。因为上传地址会过期,所以无论是第一次上传还是续传,在上传开始前都需要请求一遍上传地址;

  2. 如果第一次上传,则请求 upload.do 接口获取上传地址和文件 id,如果是续传则请求 resume.do 接口获取续传地址,续传因为不是第一次上传,所以会有文件 id,需要把文件 id 也给服务器带过去;

  3. 随后进入文件切片的阶段,文件切片通过 handle 实现,从前往后 seek 对应的 size,截取对应的 bytes,切片后需要拼接表单参数;

  4. 初始化待上传数组,数组中存储的元素是待上传的 index,上传过程中会从上传数组中取出待上传的 index,逐个分片进行上传。上传成功后,分片会插入到 finish 数组,表示已上传完成;

  5. 上传过程是并行的,并且并发数量不是固定值,而是不断进行动态计算的。上传模块有测速逻辑,并根据测速结果动态改变分片并行上传的并发数;

  6. 当发生错误时,如果是网络抖动或服务器导致上传失败,会根据对应的 case 选择是否重试,单个 task 最多重试三次;

  7. 当 task 对应的分片都上传完之后,会请求上传地址,并发送一个特殊标识,告知服务器。当前 task 文件上传完成。

03

表单提交

表单处理起来比较复杂,都是遵循标准格式,大概格式如下:

--boundary
 Content-Disposition: form-data; name="参数名"
 参数值
 --boundary
 Content-Disposition:form-data;name=”表单控件名”;filename=”上传文件名”
 Content-Type:mime type
 要上传文件二进制数据
 --boundary--

其代码实现逻辑如下,代码已脱敏,iOS 项目换一个 Boundary 就可以直接用。

表单开头和结尾都需要添加 Boundary,表示文件的边界,在不同的参数间也需要添加 Boundary,这是固定格式。代码中有一些换行操作,这些换行都是固定格式,不能增加或减少。

- (NSString *)writeMultipartFormData:(NSData *)data parameters:(NSDictionary *)parameters {
    if (data.length == 0) {
        return nil;
    }
    
    NSMutableData *formData = [NSMutableData data];
    NSData *lineData = [@"\r\n" dataUsingEncoding:NSUTF8StringEncoding];
    NSString *boundaryString = [NSString stringWithFormat:@"--%@", Boundary];
    NSData *boundary = [boundaryString dataUsingEncoding:NSUTF8StringEncoding];
    
    // 拼接上传参数
    [parameters enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
        [formData appendData:boundary];
        [formData appendData:lineData];
        NSString *thisFieldString = [NSString stringWithFormat:
                                     @"Content-Disposition: form-data; name=\"%@\"\r\n\r\n%@",
                                     key, obj];
        [formData appendData:[thisFieldString dataUsingEncoding:NSUTF8StringEncoding]];
        [formData appendData:lineData];
    }];
    
    // 拼接上传文件及信息
    [formData appendData:boundary];
    [formData appendData:lineData];
    NSString *thisFieldString = [NSString stringWithFormat:
                                 @"Content-Disposition: form-data; name=\"name\"; filename=\"filename\"\r\nContent-Type: mimetype"];
    [formData appendData:[thisFieldString dataUsingEncoding:NSUTF8StringEncoding]];
    [formData appendData:lineData];
    [formData appendData:lineData];
    [formData appendData:data];
    [formData appendData:lineData];
    [formData appendData: [[NSString stringWithFormat:@"--%@--\r\n", Boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    
    NSString *filePath = [NSString stringWithFormat:@"%@/%ld", self.segmentDocumentPath, self.currentIndex];
    BOOL write = [formData writeToFile:filePath atomically:YES];
    return write ? filePath : nil;
}
1、断点续传

文件上传整体是有断点续传的,包括 task 和分片两个维度。

如果现在 uploadTasks 队列中包含三个上传 task,第一个正在上传中,其他两个等待上传中,退出应用下一次进入应用,依然会从当前 task 的上传进度开始,并且后面两个 task 依然处于等待状态。

任务的实现很简单,退出应用以及特定时机持久化 uploadTasks 队列即可。分片的续传,以及如何保证分片可以一个不差的上传到服务器,则是一个关键问题。

  1. 对于这个问题,我设计了双数组的方式,在创建上传任务后,根据特定规则计算出单个 size 的大小,并计算源文件需要多少个分片,提前创建好对应 count 的数组,数组元素是分片索引,命名为 uploadSegments 待上传数组;

  2. 之后的上传任务都是从 uploadSegments 数组中取出 index,切片后拼接表单进行上传;

  3. 分片请求服务器后,如果请求成功则从 uploadSegments 中删除,添加到 successSegments 中。失败的话,可能遇到的 case 比较多,如果是网络波动等情况,就进行请求重试。如果是文件格式等问题,则停止上传并提示错误;

  4. 上传成功的分片 index 都会添加到 successSegments 中,直到 successSegments 的 count 等于分片的 count,所有分片任务就都上传完成,给服务器发一个标识即可完成整个任务上传;

  5. 在退出应用前,会保存 successSegments 和 uploadSegments 两个数组,下次启动后续传直接从 uploadSegments 中取出 index 后执行分片逻辑即可。没错,我们的续传是以分片为维度的。

这个方案看似比较麻烦,但对于保证上传成功率非常有效,可以解决续传、失败等多种情况。

2、内存峰值

如果是高清的视频文件,size 会比较大,1个 G 的文件也是存在的。所以,需要考虑大文件上传的问题。

需要注意的是,NSURLSession 有下面两种上传文件的方法,第二种会存在内存问题,尤其是上传较大文件时,会出现很大的内存峰值。即便是分片上传,内存峰值的问题依然存在,可能会导致上传过程中发生崩溃。

- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request 
                     fromFile:(NSURL *)fileURL;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
                       fromData:(NSData *)bodyData;

无论是用 AFNetworking 还是 NSURLSession,都存在这个问题,解决方法就是使用 fromFile 的方式解决。

所以,我们采取 fromFile 的方案,先对源文件切片,拼接表单后写入到本地,再将路径传进去上传。这样就不会出现内存峰值崩溃的问题,即便上传1个 G 的视频,整体上传流程内存依然很平稳。

图片

3、异常处理

文件上传的过程比较长,在此期间可能会遇到很多 case,下面列出一些常见问题及处理方式。

    1. 如果发生内存空间不足,或者无网络等情况,需要暂停所有任务,并保存对应任务状态;

    2. 未知网络错误,或其他非常见网络问题,重试三次,如果均失败则暂停任务;

    3. 网络未授权或飞行模式,提示用户并暂停任务。

 

 

翻译

搜索

复制

标签:方案设计,文件,formData,并行,NSString,分片,appendData,上传
From: https://www.cnblogs.com/papering/p/17818963.html

相关文章

  • vue上传文件
    以下是上传的组件库,需要更改接口<template><divclass="upload-file"><el-uploadmultiple:action="uploadFileUrl":before-upload="handleBeforeUpload":file-list="fileList":limit="li......
  • vue上传文件夹目录
    在input上面添加webkitdirectorydirectory这两个属性就能开启选择目录模式<inputref="fileIptRef"class="file-ipt"type="file"webkitdirectorydirectorymultiple@change="handleFileSelect"/>//文件上传输入框的refconstfileIptRef:any=ref(......
  • springboot3.1.5+文件上传+文件下载
    idea创建项目springbootdemo-download-upload加上thymeleaf模板maven依赖application.properties配置#thymeleaf页面缓存设置(默认为true)spring.thymeleaf.cache=false#单个上传文件大小限制(默认1MB)spring.servlet.multipart.max-file-size=10MB#总上传文件大小限制(默......
  • VUE上传文件夹的三种解决方案
     本文章向大家介绍VUE上传文件夹的三种解决方案,主要内容包括上传分步:、直接上代码、使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。 ​对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送、接收都是不可取,很......
  • uni-app上传图片后bold转base64
    uni.chooseImage({count:1,//图片张数success:asyncres=>{constreader=newFileReader();reader.readAsDataURL(res.tempFiles[0]);reader.onload=async(e)=>{console.log(e.target.result)//e.target.result转换后的base64......
  • SpringBoot集成文件 - 大文件的上传(异步,分片,断点续传和秒传)
    1.知识准备大文件的上传技术手段和普通文件上传是有差异的,主要通过基于分片的断点续传和秒传和异步上传解决。#1.1大文件面临的问题上传速度慢--应对: 分块上传上传文件到一半中断后,继续上传却只能重头开始上传--应对: 断点续传相同文件未修改再次上传,却只能重......
  • 倾斜摄影三维模型的根节点合并的并行处理技术分析
    倾斜摄影三维模型的根节点合并的并行处理技术分析 倾斜摄影三维模型的根节点合并是指将多个倾斜摄影拍摄得到的三维模型中的根节点进行合并,以减少模型大小和复杂度。在处理大规模的倾斜摄影数据时,传统的串行处理方法效率较低,因此需要使用并行处理技术来加速根节点合并的过程。......
  • 若依框架文件的上传和预览(超简版)
    若依框架文件的上传和预览(超简版)一、上传:1、首先创建一张上传文件的表droptableifexistssys_file_info;createtablesys_file_info(file_idint(11)notnullauto_incrementcomment'文件id',file_namevarchar(50)defa......
  • 在线直播源码,js 文件上传 图片上传 传输速度计算
    在线直播源码,js文件上传图片上传传输速度计算<!doctypehtml><html><head>  <metacharset="UTF-8">  <metaname="viewport"content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scal......
  • vue 大文件分片上传(断点续传、并发上传、秒传)
    对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送、接收都是不可取,很容易导致内存问题。所以对于大文件上传,采用切块分段上传,从上传的效率来看,利用多线程并发上传能够达到最大效率。本文是基于springboot+vue实现的文件上传,本文主要介绍vue实现文件上传的步骤......