弱网下视频分片上传:断点续传、排队上传、失败重试、云端溯源绑定
2026/7/12 0:36:29 网站建设 项目流程

弱网下视频分片上传:断点续传、排队上传、失败重试、云端溯源绑定

大家好,我是黒漂技术佬。视频专题第二篇,也是系列的倒数第二篇,咱们聊视频上传。

录好的视频存在本地没用,出问题的时候得能调出来看。但4G弱网环境下,传一个几MB的视频都可能断好几次,传一半断了重传又浪费流量。

今天给大家一套工业级的视频上传方案,断点续传、排队调度、优先级管理、云端绑定,全套都有。

一、为什么视频上传是个难题?

先说说4G环境下传视频的痛点:

上传的几大难题

  1. 网络不稳定:传一半断了,重传又得从头来,浪费流量
  2. 带宽有限:4G上传带宽本来就小,视频传起来慢
  3. 不能影响业务:上传占满带宽,开门指令延迟了就麻烦了
  4. 流量成本:视频文件不小,传多了流量费扛不住
  5. 要能溯源:传上去的视频得能跟订单、事件对应上

核心目标:稳定传上去,不影响业务,尽量省流量,传完能对应上。

二、分片上传与断点续传

这是大文件上传的标准方案,视频上传必须上。

什么是分片上传?

把一个大视频文件切成一小块一小块的,一块一块传:

原始视频:video.mp4 (8MB) ↓ 切片 分片1:0 ~ 1MB 分片2:1MB ~ 2MB 分片3:2MB ~ 3MB ... 分片8:7MB ~ 8MB ↓ 逐个上传 全部传完后,服务端合并成完整文件

分片大小怎么选?

分片大小优点缺点适用场景
256KB失败重传损失小请求次数多,开销大网络很差的场景
512KB均衡-推荐,大部分场景
1MB请求少,效率高失败损失大网络较好的场景

售货柜推荐:512KB一片,兼顾效率和容错。

断点续传原理

传了一半断了,下次接着传,不从头来:

  1. 上传前先问服务端:这个文件传了多少了?
  2. 服务端返回已上传的分片列表
  3. 客户端跳过已经传完的,从下一片开始传
  4. 全部传完通知服务端合并

客户端实现

publicclassChunkUploader{privatestaticfinalintCHUNK_SIZE=512*1024;// 512KB一片/** * 上传视频文件 */publicbooleanuploadVideo(StringfilePath,StringfileId){Filefile=newFile(filePath);longfileSize=file.length();Stringmd5=FileUtils.getFileMD5(file);// 1. 先查询已上传的分片List<Integer>uploadedChunks=queryUploadedChunks(fileId,md5);// 2. 计算总分片数inttotalChunks=(int)Math.ceil((double)fileSize/CHUNK_SIZE);// 3. 逐个上传未完成的分片for(inti=0;i<totalChunks;i++){if(uploadedChunks.contains(i)){continue;// 已经传过了,跳过}booleansuccess=uploadChunk(file,i,fileId,md5,totalChunks);if(!success){// 这一片传失败了,下次重试Log.w("Upload","分片 "+i+" 上传失败");returnfalse;}// 每传完一片更新进度updateUploadProgress(fileId,i+1,totalChunks);}// 4. 全部传完,通知服务端合并returncompleteUpload(fileId,md5,fileSize);}/** * 上传单个分片 */privatebooleanuploadChunk(Filefile,intchunkIndex,StringfileId,Stringmd5,inttotalChunks){// 读取分片数据byte[]chunkData=readChunk(file,chunkIndex,CHUNK_SIZE);// 构造请求MultipartBody.Builderbuilder=newMultipartBody.Builder().setType(MultipartBody.FORM).addFormDataPart("file_id",fileId).addFormDataPart("chunk_index",String.valueOf(chunkIndex)).addFormDataPart("total_chunks",String.valueOf(totalChunks)).addFormDataPart("file_md5",md5).addFormDataPart("file","chunk_"+chunkIndex,RequestBody.create(MediaType.parse("video/mp4"),chunkData));// 发送请求,带超时Requestrequest=newRequest.Builder().url(UPLOAD_URL).post(builder.build()).build();try{Responseresponse=client.newCall(request).execute();returnresponse.isSuccessful();}catch(IOExceptione){returnfalse;}}}

MD5校验

每个分片和整个文件都要校验,防止传错了:

  • 分片级:每片传完校验MD5,不对就重传这一片
  • 文件级:全部传完校验整个文件的MD5,确保完整

三、上传队列与优先级调度

不是所有视频都一样急,分优先级排队传。

优先级划分

优先级视频类型上传时机说明
最高异常事件视频立即上传异常开门、告警、人工调取
投诉/纠纷订单视频优先上传用户投诉了,后台申请调取
正常开门视频闲时上传普通交易,不急
巡检/测试视频有剩余流量再传可有可无的

上传队列实现

publicclassUploadQueueManager{privatePriorityQueue<UploadTask>queue=newPriorityQueue<>((a,b)->Integer.compare(b.getPriority(),a.getPriority()));privateUploadTaskcurrentTask;privatebooleanisRunning=false;/** * 添加上传任务 */publicvoidaddTask(UploadTasktask){// 去重:同一个文件不要重复加if(isInQueue(task.getFileId())){return;}queue.offer(task);if(!isRunning){startNextTask();}}/** * 开始下一个任务 */privatesynchronizedvoidstartNextTask(){if(queue.isEmpty()){isRunning=false;return;}// 先检查网络,弱网下不传低优先级的if(!canUploadNow()){isRunning=false;return;}currentTask=queue.poll();isRunning=true;// 异步上传newThread(()->{booleansuccess=chunkUploader.uploadVideo(currentTask.getFilePath(),currentTask.getFileId());onTaskFinished(currentTask,success);currentTask=null;startNextTask();}).start();}}

网络条件判断

弱网的时候不传或者只传高优先级的:

privatebooleancanUploadNow(){intnetworkLevel=NetworkManager.getNetworkLevel();switch(networkLevel){caseNET_LEVEL_GOOD:// 网络好,都可以传returntrue;caseNET_LEVEL_NORMAL:// 一般,只传中优先级及以上returncurrentTask.getPriority()>=PRIORITY_MEDIUM;caseNET_LEVEL_WEAK:// 弱网,只传高优先级returncurrentTask.getPriority()>=PRIORITY_HIGH;caseNET_LEVEL_BAD:caseNET_LEVEL_DISCONNECTED:// 很差,不传returnfalse;default:returnfalse;}}

四、失败重试与流量控制

失败重试策略

上传失败了怎么办?跟之前的重连一样,指数退避:

publicclassUploadRetryPolicy{privatestaticfinallongBASE_DELAY=10*1000;// 10秒起步privatestaticfinallongMAX_DELAY=10*60*1000;// 最多10分钟privatestaticfinalintMAX_RETRY=10;// 最多重试10次privateintretryCount=0;publiclonggetNextDelay(){if(retryCount>=MAX_RETRY){return-1;// 超过最大次数,放弃}longdelay=(long)(BASE_DELAY*Math.pow(2,retryCount));delay=Math.min(delay,MAX_DELAY);retryCount++;// 加随机抖动return(long)(delay*(0.8+Math.random()*0.4));}publicvoidreset(){retryCount=0;}}

上传限速

上传不能占满带宽,不然影响MQTT等核心业务:

/** * 限速输出流,控制上传速度 */publicclassRateLimitOutputStreamextendsOutputStream{privatelongbytesPerSecond;// 每秒最多传多少字节privatelongstartTime;privatelongbytesWritten;@Overridepublicvoidwrite(byte[]b,intoff,intlen)throwsIOException{// 计算当前速度,超了就sleep一下longelapsed=System.currentTimeMillis()-startTime;if(elapsed>0){longcurrentSpeed=bytesWritten*1000/elapsed;if(currentSpeed>bytesPerSecond){longsleepTime=(bytesWritten*1000/bytesPerSecond)-elapsed;if(sleepTime>0){try{Thread.sleep(sleepTime);}catch(InterruptedExceptione){}}}}out.write(b,off,len);bytesWritten+=len;}}

限速策略:

  • 正常业务期间:限制在 200KB/s 以内,不抢业务带宽
  • 闲时(凌晨):可以放开到 512KB/s 甚至更高
  • 弱网下:进一步降低,或者暂停上传

五、闲时上传与流量管控

闲时上传策略

普通视频不着急,留到流量便宜的时候传:

publicclassIdleTimeUploadScheduler{// 闲时段:凌晨2点到6点privatestaticfinalintIDLE_START_HOUR=2;privatestaticfinalintIDLE_END_HOUR=6;/** * 判断当前是不是闲时 */publicbooleanisIdleTime(){inthour=Calendar.getInstance().get(Calendar.HOUR_OF_DAY);returnhour>=IDLE_START_HOUR&&hour<IDLE_END_HOUR;}/** * 调度上传任务 */publicvoidscheduleUpload(UploadTasktask){if(task.getPriority()>=PRIORITY_HIGH){// 高优先级,立刻传uploadQueue.addTask(task);}else{// 低优先级,等闲时再传pendingIdleTasks.add(task);}}}

月流量配额控制

每个月流量有限,超了就不传非必要的:

publicbooleanhasEnoughTrafficForUpload(longfileSize){longmonthUsed=TrafficMonitor.getMonthTraffic();longmonthLimit=ConfigManager.getMonthTrafficLimit();longremaining=monthLimit-monthUsed;// 剩余流量超过文件大小的2倍才传(留余量)returnremaining>fileSize*2;}

六、云端溯源绑定

视频传上去了,得能跟订单、事件对应起来。

上传时带上业务信息

publicclassVideoUploadInfo{privateStringfileId;// 文件唯一IDprivateStringdeviceNo;// 设备编号privateStringeventType;// 事件类型:open/abnormal/shakeprivateStringrelatedOrderId;// 关联订单号privatelongstartTime;// 录像开始时间privateintduration;// 时长privateStringfileMd5;// 文件MD5privatelongfileSize;// 文件大小}

云端存储结构

视频存储/ ├── 设备编号/ │ ├── 20260708/ │ │ ├── 订单号1.mp4 │ │ ├── 异常事件_时间戳.mp4 │ │ └── ... │ └── 20260709/ │ └── ...

按设备+日期分目录,文件名带订单号或事件信息,后台管理系统可以直接按订单号查视频。

回调通知

传完了通知后台,后台把视频和订单绑定:

privatevoidonUploadSuccess(UploadTasktask){// 更新数据库状态VideoRecordDb.getInstance().updateUploadStatus(task.getFileId(),UPLOAD_STATUS_SUCCESS,System.currentTimeMillis());// 通知后台JSONObjectpayload=newJSONObject();payload.put("file_id",task.getFileId());payload.put("video_url",task.getRemoteUrl());payload.put("related_order_id",task.getRelatedOrderId());payload.put("event_type",task.getEventType());MqttManager.publish("device/"+DeviceInfo.getDeviceNo()+"/video/uploaded",payload.toString(),1);}

七、上传状态持久化

APP重启了,上传进度不能丢。

上传任务持久化

把待上传的任务存数据库:

CREATETABLEupload_task(idINTEGERPRIMARYKEYAUTOINCREMENT,file_idVARCHAR(64)UNIQUE,-- 文件IDfile_pathVARCHAR(256),-- 本地路径priorityINTEGER,-- 优先级event_typeVARCHAR(32),-- 事件类型related_idVARCHAR(64),-- 关联IDstatusINTEGERDEFAULT0,-- 0待上传 1上传中 2成功 3失败uploaded_chunksTEXT,-- 已上传分片列表(JSON)retry_countINTEGERDEFAULT0,-- 重试次数create_timeBIGINT,update_timeBIGINT);

启动恢复

APP启动时,从数据库恢复未完成的任务:

publicvoidrestorePendingTasks(){List<UploadTask>pending=UploadTaskDb.getInstance().queryPendingTasks();for(UploadTasktask:pending){if(task.getStatus()==UPLOAD_STATUS_UPLOADING){// 上次传了一半,重置为待上传,从断点继续task.setStatus(UPLOAD_STATUS_PENDING);}uploadQueue.addTask(task);}}

八、踩坑经验

坑1:分片传完了但合并失败

  • 现象:所有分片都显示传成功了,但合并后的文件损坏
  • 原因:某一片传的时候数据错了,MD5没校验好
  • 解决:每片传完服务端都校验MD5,不对就让重传;合并后再校验整个文件的MD5

坑2:上传过程中网络切换

  • 现象:传一半WiFi切4G,IP变了,连接断了
  • 解决:分片上传天然支持断点续传,断了重新查进度接着传就行

坑3:视频还在录制就开始传

  • 现象:文件还在写,读出来的数据不完整
  • 解决:录制完成、文件关闭后再加入上传队列;上传前检查文件是否完整

坑4:大量视频同时上传把带宽占满

  • 现象:批量恢复上传的时候,几十个视频一起传,MQTT都连不上了
  • 解决:单队列串行上传,同时只传一个;加上传限速;弱网下只传高优先级

九、总结

弱网视频上传的核心:分片+断点+排队+限速

  • 分片上传:512KB一片,失败只重传一片,省流量
  • 断点续传:传了多少都记录,重启断网都不怕
  • 优先级队列:异常、投诉优先,普通视频闲时再传
  • 上传限速:不能抢业务带宽,保证开门等核心功能优先
  • 流量控制:月配额、闲时策略,控制成本
  • 溯源绑定:视频和订单、事件关联,方便后台查询

这套方案下来,4G弱网环境下视频上传也能稳稳的,不用再担心传一半断了白忙活。


我是黒漂技术佬,专注无人零售物联网实战。五大模块的12篇细分文章到这里就全部结束了,下一篇是整个系列的收官总结,把全链路的核心要点和踩坑经验串起来。

有视频上传的坑,欢迎私信交流,大家一起避坑~

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询