Java大文件上传方案:切片、秒传与断点续传实战

发布时间:2026/10/8 2:45:41
Java大文件上传方案:切片、秒传与断点续传实战 在金融保险系统里摸爬滚打过的朋友应该都有体会保单扫描件、理赔影像资料、银行对账单Excel、带电子签章的Word合同这些文件的体积动不动就是几十MB甚至上GB还一堆文件放在同一个文件夹里批量上传。传统上传方式在这种场景下简直是一场灾难——传一半断掉、超时重传、服务器内存被打满业务人员盯着进度条干着急运维那边CPU和带宽也跟着告急。我在项目里反复折腾过好几轮最终的解法就是标题里说的这套东西用切片算法把大附件拆碎配合秒传和断点恢复把上传体验从“提心吊胆”变成“无感完成”。这篇文章就把我在金融保险系统中的完整落地思路、关键代码和踩过的坑一次讲清楚适合正在做Java文件上传模块、尤其是被大文件和弱网环境折磨的同学参考。1. 先看清场景金融保险的大附件上传到底难在哪1.1 保单材料不是普通文件是“又大又多又急”金融保险业务里的附件上传和一般论坛发个图片、社区传个头像完全不是一个量级。用户投保时要传身份证扫描件、收入证明、体检报告理赔时要传诊断书、费用清单、发票影像这些材料通常是PDF扫描件或者高清图片打包后的文件夹单文件几十MB属于常态文件夹整体上GB也不稀奇。再加上Word版合同、Excel对账单这类办公文档格式标准不统一里面可能还嵌着图片和宏读起来更费劲。难点还不只在大。金融业务对文件完整性和时效性要求极高理赔材料传一半丢了用户投诉事小影响核赔时效、引发合规问题事大。而且这类系统往往部署在保险公司的内网环境里有的还经过多层安全设备和网关网速并不稳定遇到高峰期或者跨地域传输大文件上传失败率能到三四成。我接过一个实际案例某分公司批量上传理赔影像每天都有人传失败后台日志里全是“连接被重置”“请求超时”业务部门怨声载道。这种场景下传统的单次HTTP上传方案基本是撑不住的。核心问题在于请求一次发出数据量越大中间链路出问题的概率就越高而一旦出错就要从头再来。这不是网络的问题是方案设计的问题。1.2 秒传和断点本质上是两个不同的问题很多人把秒传和断点混在一起说其实它们要解决的事情完全不同只不过在切片方案里天然地被整合到了一起。秒传的核心是“去重”。假设同一个用户今天传了某个Word合同明天又要传一次或者不同机构上传了内容完全相同的对账单那么服务端其实已经有这份文件了就没必要让数据再走一遍网络。秒传要回答的问题是如何快速判断“这个文件和服务器上的某个文件是同一个”答案通常是做指纹比对最常用的就是MD5。只要文件内容一致MD5就一致服务器直接告诉客户端“你传过了我帮你标记成功”从用户角度看就是瞬间完成。断点的核心是“恢复”。如果传输过程中网络断了、服务重启了、甚至用户手动关了页面下一次再传的时候不应该从头开始而是跳过已经传完的部分只补发缺失的部分。这跟视频App里的断点续播是一个道理——看到35分钟退出下次直接从35分钟接着看而不是让你重新看一遍。断点要回答的问题是如何精准记录“传到了哪一块”这就天然对应上了切片既然文件被拆成了分片那就记录每个分片的状态哪个传成了、哪个没传成一目了然。把这两个东西想清楚了方案设计就有了主线切片是为了断点恢复指纹是为了秒传去重两者共享一套分片状态管理机制。1.3 为什么选切片而不是流式上传或断点重传早期我见过不少项目走的是“断点重传”路线不拆分文件只记录HTTP请求的字节偏移量下次从偏移处接着传。这种方式在局域网里能跑拿到金融保险这种复杂网络环境下就问题不断。原因是很多网关、代理服务器对请求体有大小限制超过一定阈值就直接断掉而且流式上传的进度很难校准中间任何一次写盘失败偏移量就对不上了。切片方案则从根本上规避了这些问题。它把一个大文件切分成若干个独立的小分片每个分片用一次单独的HTTP请求上传服务端收到后先落盘最后再按顺序合并。这样一来单次请求的数据量被控制在一个很小的范围网络波动、网关限制的影响被降到最低分片之间的状态相互独立某个分片失败只需要重传那一片而且分片可以并行上传充分利用带宽整体速度反而比单流上传更快。我在金融保险系统里最终采用的是固定大小切片默认4MB一片关键路径上的设计要素包括分片序号管理、服务端分片存储结构、状态记录机制、合并与校验流程。下面依次拆开讲。2. 整体方案设计切片的粒度、状态与存储怎么定2.1 切片大小不是拍脑袋先算网络和内存的账切片大小是方案里第一个要决策的参数。切小了分片数量多请求次数成倍上涨服务端要频繁写小文件数据库状态记录也跟着膨胀切大了又失去了切片的意义单次请求的压力重新变大。我一般按两个维度来考量。第一是网络维度金融保险系统的内网环境通常能跑5到20MB/s的带宽外网接入层可能会更低。如果希望单个分片在5秒内传完切片大小就该控制在带宽乘以5秒的量级。按内网10MB/s算4MB到8MB比较合适如果外网带宽只有1到2MB/s切成1MB到2MB更稳。第二是内存维度分片上传往往要做MD5和临时写盘如果客户端一次性把整个分片读进内存4MB的分片对应的缓冲数组也就是4MB这个量级在JVM里完全无压力但如果切成32MB一片并发传5个分片内存占用就上去了。在保险公司的老服务器上堆内存经常只给了512MB到1GB这种场景下我宁可多切几片也不冒险让内存打满。我最终的保守选择是内网环境用4MB外网弱网环境动态下探到2MB。代码里可以通过配置中心动态调整不需要重新发版。2.2 分片序号与请求标识的设计分片上传最怕的是乱序和串档。我先定了一套命名和标识规范然后在代码层强制约束。每个上传任务生成一个全局唯一的 uploadId用UUID就行。每个分片的请求里必须带上四个核心参数文件唯一指纹MD5、上传任务ID、分片序号、总分片数。服务端收到的分片文件统一按 “工作目录/uploadId/序号” 存储比如/data/upload_temp/8f3c2a11-xxxx-xxxx-xxxx-xxxxxxxxxxxx/ ├── 0.part ├── 1.part ├── 2.part └── 3.part分片序号从0开始递增和文件切分的字节区间严格对应。比如4MB一个分片第0片对应 [0, 4194304)第1片对应 [4194304, 8388608)最后一片不足4MB就按实际长度来。这里有个关键点分片序号不能由客户端随意指定顺序必须按偏移量计算。我见过有人按文件名排序来编号结果某些系统里文件名排序是字典序“10.part”会排在“2.part”前面合并的时候就乱套了。所以我坚持只传纯数字序号合并时按序号大小排序绝不依赖字符串排序。2.3 秒传指纹与分片状态分开存储状态存储我用了一张表加Redis缓存的组合。数据库表用来做最终一致性保证Redis用来做高频状态查询。数据库表的数据结构大致是字段类型说明upload_idvarchar(64)上传任务全局唯一IDfile_md5varchar(64)整个文件的MD5用于秒传判断file_namevarchar(255)原始文件名total_sizebigint文件总字节数chunk_sizeint每个分片的字节数total_chunksint总分片数received_chunkstext已收到的分片序号集合用逗号分隔statusint0初始化1上传中2合并中3完成4失败create_timedatetime创建时间update_timedatetime更新时间Redis里的设计更轻量用uploadId作为key用二进制位图标记哪些分片已经到达。比如总共100个分片位图第i位是1就表示第i片已收到。合并前扫描位图一眼就能看出缺了哪些分片。分片状态的设计有一个容易漏掉的细节已上传的分片在合并之前不能直接当成功。因为分片可能传了一半服务端写盘失败或者文件校验和不对。所以我在接收分片的逻辑里除了记录“收到”还会校验分片自身的MD5校验不过就把状态置为失败并要求重传。分片校验和整文件校验是两道独立的关卡。2.4 服务端目录结构与临时文件管理临时文件的目录管理我吃过几次亏之后总结出一套比较稳的规范。工作根目录按月建目录比如 /data/upload_temp/202506/每个上传任务再建一个以uploadId命名的子目录。分片文件直接写进子目录合并完成后整个子目录移到归档区或者直接删除。要注意的点是切分目录的清理策略。金融保险系统里的文件不允许随意删除但临时分片文件属于中间产物过了保留期就该清理。我一般加了一个定时任务扫描超过24小时未完成的上传任务先把状态置为“过期”再删除临时目录。过期时间设在业务低峰期执行避免影响正在上传的请求。这里最大的教训是千万别把分片文件直接写到业务文件目录里。我见过有人图方便把分片直接落在网盘目录下结果用户还没上传完网盘同步客户端就把半拉子分片同步走了最后合并时出现“文件被占用”的诡异报错。3. 秒传是怎么实现的文件指纹与查重链路3.1 MD5做秒传依据够用但不万能秒传的核心逻辑是拿“整个文件”的MD5去服务端查一次如果命中就直接返回成功。这个策略在最常见的场景下是可靠的同一个文件内容一致MD5就一致内容不同MD5冲突的概率在工程实践中可以忽略。但是金融保险系统得考虑一个隐患MD5本身是摘要算法理论上存在碰撞可能而且在信息安全的语境里MD5已经被认为不够安全。现实中我们不会拿MD5当加密用只是当文件唯一性标识碰撞概率非常低用在秒传场景是够的。为了进一步提高可靠性我在指纹里叠加了文件大小MD5一致且大小一致的才判定为已存在双因子排除掉绝大多数极端情况。有些要求更严的金融机构会要求用SHA-256替代MD5代价是计算性能略低大文件初始化扫描更慢。这个可以做成可选配置我只在核心节点的秒传判断上同时计算两种摘要生产上绝大多数服务还是走MD5。另外要注意计算MD5的时机。对于几十MB的文件全量读一遍计算MD5大概要几百毫秒到1秒这个延迟用户能接受但如果是几个GB的文件就不太合适了。我的做法是客户端在启动上传前异步计算整个文件的MD5计算完成后再正式发起秒传请求。如果这个文件之前已经算过并且有缓存记录直接复用连算都不用算。3.2 秒传判断的前置流程秒传不是“传完了再判断”那样就失去意义了。正确顺序是客户端先算好MD5带着MD5和文件大小访问秒传接口服务端查库命中就返回“无需上传”同时把文件在存储系统中的地址返回给客户端用于后续业务关联。我把秒传接口设计为三步第一步客户端调用 preCheck 接口参数是 fileMd5、fileSize、fileName。服务端先在文件指纹表里查如果命中返回 code1秒传成功直接结束。如果没有命中返回 code0继续走正常上传。第二步服务端在返回 code0 的同时创建 uploadId 和分片参数把总片数、每片大小一并返回给客户端。客户端拿到这些参数后开始切分并逐个上传分片。第三步全部上传完成后客户端调用 complete 接口服务端做分片合并和整文件MD5复算更新指纹表。这样下次再有人传相同文件第一步就直接秒传了。这里有一个容易被忽略但很影响体验的环节第一次传的文件如果在中途失败了指纹表里不应该有记录但如果所有分片都传完了只是合并时出了错指纹表已经写了怎么办所以我把指纹表的写入放在了合并成功之后失败时不写宁可让用户重新触发一次秒传判断。3.3 秒传的“双写校验”逻辑只靠客户端的MD5来做秒传判断存在一个信任问题客户端完全可以伪造一个假的MD5。这在对外网开放的保险业务场景里是个隐患——如果有人恶意提交一个已知MD5的假请求服务端就认为文件上传成功但实际并没有任何真实数据落盘。我的做法是双写校验秒传请求命中指纹表后服务端不会只返回一个成功状态而是把这个文件的实际存储地址、大小、最后一个分片的MD5一并返回。前端拿到这些元数据后可以在业务侧做展示但真正的文件读取还是走后端存储系统不走前端透传。这就把“秒传是不是真的成功”的判断权收回到服务端手里。另外还要补一个安全细节秒传命中后文件在存储系统中的归属权限要重新绑定。保险业务里不同机构之间的文件权限是隔离的A机构上传的文件B机构不能因为MD5相同就直接访问。所以秒传成功后我会做一次文件归属拷贝把文件从公共指纹区复制一份到当前业务空间而不是简单地返回一个公共地址。4. 断点续传的落地细节状态记录、恢复与并发控制4.1 恢复不等于重传先搞清楚缺了哪些分片断点续传的实现最核心的不是“怎么继续传”而是“怎么知道哪些传过了”。我采用的状态管理方案是服务端记录 客户端记录双重的。服务端的记录来自保存到数据库和Redis的分片状态。客户端每次启动上传前先调用 queryUploadStatus 接口传入 uploadId服务端返回一个分片序号列表已经收到的返回1缺失的返回0。客户端拿到这个列表后只上传缺失的分片。这有个天然的好处即使客户端本地状态完全丢失比如换了一台电脑只要 uploadId 还在服务端就能把已传分片告诉新的客户端。当然保险场景里大多数是同一个客户端重复上传但服务端状态兜底是必须的。客户端这边也要做一份本地状态缓存。我在客户端用一个JSON文件记录每个分片的上传状态放在临时目录里文件名就是 uploadId.json。里面包含分片序号、是否上传成功、分片的MD5、上次上传时间。每次上传分片成功后立即更新这个JSON文件。这样做的价值在于网络断掉连不上服务端的时候客户端本地还能知道传到了哪里可以省掉一次状态查询。4.2 合并前的一次性校验分片全部上传完之后服务端不能直接合并。我加了一道二次校验遍历所有分片文件核对数量是否等于 totalChunks大小是否总和等于 totalSize再抽几片算一下分片MD5是否与上传时记录的MD5一致。校验通过才允许执行合并。这一步在实际业务中救过我好几次。有一回某个分片因为网络抖动HTTP请求返回了200但服务端写盘时发生部分写入文件长度比预期小了几个字节。如果不校验合并出来的文件就是损坏的核赔系统读取理赔影像时往往不会立刻报错等用到第N页才发现数据错位那时候问题就大了。加了合并前校验后这种情况在源头就被拦截客户端会收到提示重新上传该分片。4.3 并发上传与线程池设计分片天然支持并行上传但并发数不是越大越好。客户端上传分片时我按机器性能和网络带宽做了并发数动态调整默认4个并发弱网环境降到2个内网千兆环境下最多开到8个。实现上就是线程池 计数器提交N个分片到线程池每个分片上传成功后计数器减一任一失败则把当前批次标记为失败。这里最关键的细节是幂等同一个分片允许被重复上传重复上传时服务端直接覆盖同名文件不影响其他分片。因为网络超时导致客户端实际已经上传成功、但客户端以为失败的情况很常见允许重传同一个分片是保证最终一致性的基础。服务端接收分片的接口也必须是幂等的。我设计成如果该分片已存在且大小一致直接返回“已有”不重复写盘。这样即使客户端重复提交也不会造成资源浪费。4.4 合并时的IO性能优化合并阶段最怕的是把分片一个个全读进内存再写文件大文件夹场景下很容易OOM。我的合并代码用的是 NIO 通道 transferTo 方法流式地将分片内容搬运到目标文件不经过JVM堆内存。这个做法在保险公司的老机器上运行得很稳。另外合并时还要考虑磁盘空间。临时分片和合并文件同时存在峰值磁盘占用接近原文件的两倍。所以在合并流程中每写完一个分片的内容就删除对应的 .part 文件实时释放空间。这样峰值只维持在原文件大小的基础上多一点点而不是两倍。5. 核心代码实现从切分、上传到合并的完整链路5.1 客户端切分与分片初始化客户端的切分逻辑用 RandomAccessFile 实现。先读文件长度然后按 chunkSize 从0字节偏移开始循环截取。每个分片除了原始数据还要在请求头里带上 uploadId 和 chunkIndex。public ListFileChunk splitFile(File file, int chunkSize) throws IOException { ListFileChunk chunks new ArrayList(); long fileLength file.length(); int chunkIndex 0; try (RandomAccessFile raf new RandomAccessFile(file, r)) { byte[] buffer new byte[chunkSize]; long offset 0; while (offset fileLength) { raf.seek(offset); int readLength raf.read(buffer, 0, (int) Math.min(chunkSize, fileLength - offset)); FileChunk chunk new FileChunk(); chunk.setIndex(chunkIndex); chunk.setOffset(offset); chunk.setLength(readLength); chunk.setData(Arrays.copyOfRange(buffer, 0, readLength)); chunks.add(chunk); offset readLength; } } return chunks; }这段代码是直接可跑的注意最后一片的长度是不足 chunkSize 的拷数据时用 copyOfRange 截断避免把缓冲区的空位带上。5.2 秒传预检与上传任务初始化秒传预检接口用Spring Boot的Controller实现核心逻辑是先查指纹表再决定是直接返回成功还是创建上传任务。PostMapping(/upload/preCheck) public Result? preCheck(RequestBody PreCheckRequest req) { FileFingerprint fp fileFingerprintMapper.findByMd5AndSize(req.getFileMd5(), req.getFileSize()); if (fp ! null) { // 命中指纹秒传成功 return Result.success(ImmutableMap.of( fastUpload, true, fileId, fp.getFileId(), storagePath, fp.getStoragePath() )); } // 未命中创建上传任务 String uploadId UUID.randomUUID().toString(); int chunkSize resolveChunkSize(req.getFileSize(), req.getClientEnv()); int totalChunks (int) Math.ceil((double) req.getFileSize() / chunkSize); UploadTask task new UploadTask(); task.setUploadId(uploadId); task.setFileMd5(req.getFileMd5()); task.setFileSize(req.getFileSize()); task.setFileName(req.getFileName()); task.setChunkSize(chunkSize); task.setTotalChunks(totalChunks); task.setStatus(0); uploadTaskMapper.insert(task); return Result.success(ImmutableMap.of( fastUpload, false, uploadId, uploadId, chunkSize, chunkSize, totalChunks, totalChunks )); }注意 resolveChunkSize 要根据客户端网络环境动态计算我现在是读配置中心的参数内网默认4194304字节外网弱网默认2097152字节。5.3 分片接收与状态落库分片接收的接口用 MultipartFile 接收内容写到 uploadId 对应的目录中。写完文件之后更新Redis位图和数据库 received_chunks 字段。PostMapping(/upload/chunk) public Result? uploadChunk(RequestParam(file) MultipartFile file, RequestParam(uploadId) String uploadId, RequestParam(index) Integer index) { // 校验上传任务状态 UploadTask task uploadTaskMapper.findByUploadId(uploadId); if (task null || task.getStatus() ! 0) { return Result.error(上传任务不存在或已关闭); } // 幂等判断该分片已存在且大小一致直接返回成功 Path chunkPath Paths.get(BASE_DIR, uploadId, index .part); if (Files.exists(chunkPath)) { long existSize Files.size(chunkPath); if (existSize file.getSize()) { markChunkReceived(uploadId, index); return Result.success(); } } // 写入分片文件 Files.createDirectories(chunkPath.getParent()); try (InputStream in file.getInputStream()) { Files.copy(in, chunkPath, StandardCopyOption.REPLACE_EXISTING); } markChunkReceived(uploadId, index); return Result.success(); }markChunkReceived 方法里做两件事把Redis位图对应位置1同时更新数据库的 received_chunks 字符串。更新数据库时用追加逗号加序号的方式不用每次读全量再覆盖。5.4 合并与整文件校验合并前先校验完整性再执行文件拼接。拼接我用的是 FileChannel 的 transferTo可以理解成操作系统级的零拷贝搬运内存占用极低。public File mergeChunks(String uploadId, String dstPath) throws IOException { UploadTask task uploadTaskMapper.findByUploadId(uploadId); // 1. 完整性与大小预校验 if (task.getTotalChunks() ! listChunks(uploadId).size()) { throw new IllegalStateException(分片数量不匹配缺失部分分片); } // 2. 按序号排序后顺序拼接 Path dest Paths.get(dstPath); try (FileChannel outChannel FileChannel.open(dest, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) { for (int i 0; i task.getTotalChunks(); i) { Path part Paths.get(BASE_DIR, uploadId, i .part); if (!Files.exists(part)) { throw new IllegalStateException(缺少分片: i); } try (FileChannel inChannel FileChannel.open(part, StandardOpenOption.READ)) { long remaining inChannel.size(); long position 0; while (remaining 0) { long transferred inChannel.transferTo(position, remaining, outChannel); position transferred; remaining - transferred; } } Files.delete(part); // 合并完立即删除分片 } } return dest.toFile(); }合并完成后还要对最终文件重新计算MD5和客户端上传时的 fileMd5 比对。不一致就删除合并文件并报错一致才允许更新指纹表把状态置为完成。这一步是整个链路里绝对不能省的最后一道门槛。5.5 大文件夹批量上传的任务编排单个文件切片方案做好后文件夹批量上传就是在这个基础上的任务编排问题。我实现的思路是先扫描文件夹生成文件清单按文件大小从大到小排序然后并行发起多个文件的上传任务。每个文件的上传流程和单文件完全一致互不干扰。文件夹里不同文件之间没有顺序依赖但同一文件的分片之间不能跨文件并发否则服务端不便管理。所以我为每个文件单独分配一个线程池避免同一个文件的多个分片被不同的线程乱序处理。总体并发数控制在一个上限之下比如同时最多处理5个文件每个文件最多4个分片并发。进度统计也要按文件夹维度汇总总文件数、已完成文件数、当前文件的分片进度前端展示的时候按文件夹整体进度来用户体验比单文件进度好得多。6. 常见问题与排查技巧实录6.1 问题速查表我整理了一份在金融保险系统环境里实际遇到过的典型问题附带现象描述和排查思路。问题现象可能原因排查与解决秒传总是提示成功但文件打不开指纹表提前写入合并尚未完成检查完整合并后再写指纹表确认 status 为3时才返回秒传成功分片传完后合并报“缺少分片”分片文件被清理定时任务误删清理任务的过期时间应大于上传任务最大生命周期建议至少24小时上传到一半服务端内存飙升分片太大或并发数太高检查线程池并发数降低 chunkSize或改用 NIO 流式写盘弱网环境下分片反复失败单次请求超时时间设置太短客户端HTTP超时时间按“分片大小/最低带宽”计算至少30秒合并后文件MD5与原始MD5不一致客户端切分时数据错位或服务端写盘不完整给每个分片额外记录独立MD5合并前逐个校验分片MD5断点恢复后进度丢失客户端本地状态文件未同步确保每个分片上传成功后立即更新本地JSON缓存恢复时优先读取本地状态文件夹上传时某些文件秒传不生效指纹只匹配内容不同文件名不触发秒传指纹匹配应忽略文件名差异只比对MD5和文件大小页面显示100%但业务侧查不到文件合并完成后未更新业务表的文件关联合并成功并迁移到正式目录后再更新业务表的 fileId 和 storagePath6.2 几个必须注意的实战细节第一个细节是HTTP客户端超时设置。很多人默认用30秒超时弱网下传4MB分片可能不够我在客户端里按“分片大小除以预估最低带宽再加10秒”动态计算超时时间。比如预估带宽512KB/s4MB分片至少8秒超时就设到20秒以上。这个细节不处理弱网下断点续传会被大量假失败淹没。第二个细节是服务端接收分片的幂等。客户端在网络超时后重传同一个分片是常态服务端接口必须能识别“这个分片我已经收过了”。我的做法是同名分片文件已存在且大小一致直接返回成功。这个设计让断点恢复逻辑变得非常简单——客户端只需要无脑补传缺失分片重复传也安全。第三个细节是状态更新的顺序。先写Redis位图再更新数据库可以减少对数据库的压力。但如果Redis和数据库都更新完然后合并失败要把状态回滚成可重试。我的做法是合并失败后不删分片只把status改成4客户端拿到失败状态后可以从任意缺失分片继续上传。6.3 上线前一定要做的压力测试金融保险系统上线这种切片上传功能不能只在小规模环境里测一下就完事。我给团队定的测试标准是单机2万并发分片请求、连续跑1小时观察服务端线程池饱和度和磁盘IO。实测中最大的瓶颈往往不是应用本身而是临时目录所在磁盘的IOPS尤其是机械硬盘上合并大文件时特别慢。所以我会要求在SSD上单独划一块分区给临时目录业务文件落在另一个存储上两者互不干扰。还有一点是关于完整性的抽检逻辑。压测时不能只看所有请求都是200要在压测结束后随机抽取上传任务把合并后的文件和原始文件做字节级比对。我见过不少系统压测时服务端没有报错但某个分片因为并发写盘的时序问题写入的数据错位了导致合并文件字节数和原始文件一致但内容不对。字节级比对是最好的兜底测试。6.4 关于“忽略不需要的步骤”工程上有一个很重要的经验就是断点恢复时不要盲目地把所有分片都重传一遍。正确的顺序是先调用状态查询接口拿到服务端已确认的分片列表把客户端本地状态和服务端状态做交集然后只上传两边都确认缺失的分片。这个逻辑我用过一个很形象的比喻来形容这就好比看视频你拖到35分钟播放器只需要下载35分钟之后的数据35分钟之前的内容已经存在本地缓存里了没必要重新下载。实际生产里由于网络抖动导致某些分片上传成功但客户端没收到响应的情况很常见。这种情况下客户端本地会把该分片标记为失败但服务端状态是成功。恢复的时候如果只看客户端状态就会多传一片只看服务端状态又可能漏掉真正缺失的。所以必须取“两边状态的差集”而不是简单用任一边的判断。这也是断点续传做得严谨与否的分水岭。7. 如果再让我做一次我会调整什么这套方案在几个保险业务项目里跑了一年多整体稳定但我复盘下来有几个点如果能重来会在一开始就调整。第一是把文件指纹从MD5直接升级成“MD5文件大小SHA-256”三重校验早期为了兼容老系统只用了MD5后期再改就涉及数据迁移和存量文件重算麻烦很多。第二是给临时分片文件加一层加密存储金融监管对敏感影像资料的静态加密有明确要求当时是先上线后补的中间有过一段不合规风险期。第三是分片并发参数应该从配置中心动态下发而不是写死在客户端的配置文件里我刚上线时就因为不同分公司网络差异大被迫做了两版配置。最后想提醒后来者的是切片算法的代码实现本身并不复杂真正难的是把“网络异常”“服务重启”“文件损坏”“重复提交”这些非正常路径都想清楚。我在开发这个模块的大半年时间里至少有一半的时间花在测试这些异常分支上。切片上传没有银弹但只要你把状态管理做实、把幂等做透、把校验做足这套方案在金融保险这种对可靠性要求苛刻的场景里是完全能扛住的。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询