简介:面向 Java 开发者的 MinIO 分片上传与断点续传示例,适用于需要实现大文件高效上传、网络中断后续传的 Web 应用场景。示例同时提供后端与前端实现,后端仅引用必要依赖,启动时修改配置文件中的 MinIO 服务地址、端口与密钥即可运行;前端页面通过脚本完成文件切片、MD5 校验与上传调度,并搭配轻量样式,便于观察各阶段状态。资源包共 13 个文件,其中 7 个 Java 源文件对应后端接口与测试代码,另有前端脚本、页面、样式以及 XML、properties 等配置,压缩包体积仅 19KB,部署验证非常快捷。自发布以来已有 20968 人学习/下载,适合希望快速掌握 MinIO 分片上传、断点续传流程的初中级 Java 开发者。使用者可从前端 composeFile 函数的注释入手理解切片与合并逻辑,再对照后端接口完成联调,最终得到一份可直接测试的最小可用示例,作为实际项目改造的起点。
1. 先搞清楚:MinIO分片上传在Java项目里到底解决什么问题
做过对象存储接入的Java开发者,大概率遇到过这样的场景:要上传一个几个GB的压缩包或视频文件,直接一个InputStream往MinIO里塞,结果要么内存吃紧,要么网络抖一下整个上传失败、重新来过。MinIO的Java SDK原生支持分片上传(Multipart Upload),把大文件切成若干个分片逐个上传,最后一次性合并。这个机制配合断点续传,能让大文件上传从“赌网络”变成“可预期的任务”。这篇笔记会从MinIO分片上传的底层流程讲起,给出能直接跑起来的Java示例,再把我调优线程池、分片大小和续传逻辑的踩坑过程完整写出来。
适合谁读:正在用MinIO做文件服务、想把大文件上传的稳定性和速度提上去的同学;以及已经被“上传到一半断掉只能重传”折磨过的人。内容不依赖具体框架,Spring Boot、纯Java项目都能照搬这套思路。
2. MinIO分片上传的运作机制:三个API加一个uploadId
2.1 三个核心API:initiateMultipartUpload、uploadPart、completeMultipartUpload
MinIO兼容S3协议,所以它的分片上传流程和AWS S3的Multipart Upload本质上是一套东西。整个流程可以用三个动作概括:
第一步,发起一个初始化请求。服务端会生成一个全局唯一的uploadId,这个ID是整个分片上传任务的“身份证”。第二步,拿着uploadId和分片编号,把文件的每一段数据上传上去。每个分片上传成功后,服务端会返回一个ETag,也就是这段数据的校验值,后面合并时要用到。第三步,把所有分片的ETag按顺序拼成一个列表,发给服务端做合并。
用Java SDK来写,大致是这个骨架:
// 1. 初始化分片上传,拿到uploadId String uploadId = minioClient.initiateMultipartUpload(bucketName, objectName); // 2. 逐片上传 for (int partNumber = 1; partNumber <= totalParts; partNumber++) { PartETag eTag = minioClient.uploadPart(bucketName, objectName, uploadId, partNumber, partStream, partSize); partETags.add(eTag); } // 3. 合并分片 minioClient.completeMultipartUpload(bucketName, objectName, uploadId, partETags);这里有个容易被忽略的细节:上传分片时的partNumber必须从1开始连续编号,不能跳号。如果中间某个分片上传失败,后续分片还能继续传,但合并时缺了任何一段都会失败。所以实际项目中,分片上传的代码一定会维护一个“哪些分片已经上传成功”的清单,这正是断点续传的数据基础。
2.2 分片大小怎么定:5MB不是拍脑袋定的
分片大小是整个上传方案里最影响性能的一个参数。MinIO服务端要求最小分片为5MB,这是硬性下限。但实际项目中,我一般不会真的用5MB分片,除非是并发上传的测试Demo。
原因有两个:一是分片越小,分片数量越多,每次uploadPart都要发起一次HTTP请求,网络往返的开销会拖慢整体速度;二是合并时服务端要把所有分片的元数据列出来,分片数量上万时,这个列表本身就会占用不小的内存和CPU。反过来,分片也不能无限大,过大的分片在失败重试时,重新传输的代价会很高。
我通常按文件大小分三档来选择:
| 文件大小 | 推荐分片大小 | 分片数量级 | 说明 |
|---|---|---|---|
| 5MB ~ 100MB | 5MB ~ 10MB | 10 ~ 20片 | 单机小文件,不必开太多并发 |
| 100MB ~ 2GB | 16MB ~ 32MB | 30 ~ 120片 | 最常见的业务文件,平衡并发和重试成本 |
| 2GB 以上 | 64MB | 30片以上 | 大文件,优先减少分片数量,靠并发拉速度 |
这个表格的思路是:分片数量尽量控制在100片以内,因为MinIO控制台和服务端日志在处理上千个分片的合并请求时,响应时间会有明显上升。分片大小的计算可以用文件总长度除以目标分片数,然后向上对齐到5MB的整数倍。注意,最后一片的大小可以小于分片大小,这是被允许的。
2.3 并发上传与分片顺序:能乱序传,但合并必须有序
MinIO允许分片以任意顺序上传,因为每个分片的partNumber是明确的,服务端会按编号把它们存好。这意味着我们可以用线程池并发上传多个分片,把网络吞吐打满。但合并时,传给completeMultipartUpload的PartETag列表必须严格按partNumber升序排列,顺序错了合并就会失败。
这是一个典型的“并发写入、串行提交”模式。我会在代码里用一个ConcurrentHashMap或PriorityBlockingQueue来保存已上传分片的ETag,全部完成后转为有序列表再提交合并请求。另外一个常见做法是使用ExecutorService,提交N个分片上传任务,等到所有Future都返回成功后,再对PartETag集合做排序。
并发数的选择我放到后面调优部分详讲,这里先给一个基线值:分片数量少于30片时,并发线程数设为分片数本身即可;分片数量多时,线程数保持在CPU核心数的2到4倍,不要盲目开大。
3. 搭建可复现的分片上传Demo:依赖、客户端和核心代码
3.1 工程结构:只需要一个类就跑通
分片上传的代码不依赖复杂框架,纯Java工程加两个依赖就行。我常用的SDK是MinIO官方Java客户端,它在7.x版本之后把分片上传的底层操作封装得很清晰,同时保留了直接调用底层API的能力。
<dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.5.7</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>2.0.9</version> </dependency>注意版本号要结合你当前的JDK和项目情况来定。这里写版本号是为了说明依赖坐标,实际使用时请换成你自己项目验证过的版本。第二个依赖是日志实现,MinIO客户端内部依赖SLF4J,不加日志实现的话控制台看不到任何输出,排查问题会很痛苦。
工程结构上,我建议拆成三个类:一个是MinIO客户端的初始化类,一个是分片上传核心逻辑类,另一个是断点续传的检查点管理类。分开写的原因很简单:分片上传和断点续传虽然在同一篇笔记里,但它们各自的职责不同,混在一个类里后期改参数、加逻辑时会互相牵扯。
3.2 初始化MinIO客户端:连接池和超时是第一个性能开关
MinIO客户端的初始化看似简单,很多人写一行代码就完事了,但连接管理、超时时间这些参数会在分片上传场景下被放大。分片并发上传意味着同一时间会有多个HTTP连接打向服务端,如果客户端底层连接池不够,分片请求会排队等待,性能直接躺平。
import io.minio.MinioClient; public class MinioConfig { public static MinioClient createClient(String endpoint, String accessKey, String secretKey) { return MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); } }这个默认初始化方式在分片上传时,每个uploadPart调用都会单独建立连接。要改善这个问题,可以在构建客户端时指定连接池和超时配置。MinIO Java SDK在底层依赖OkHttp,所以连接池的配置要通过OkHttpClient来做:
import io.minio.MinioClient; import okhttp3.OkHttpClient; import java.time.Duration; public class MinioConfig { public static MinioClient createClient(String endpoint, String accessKey, String secretKey) { OkHttpClient httpClient = new OkHttpClient.Builder() .connectTimeout(Duration.ofSeconds(30)) .readTimeout(Duration.ofMinutes(5)) .writeTimeout(Duration.ofMinutes(5)) .connectionPool(new okhttp3.ConnectionPool(50, 5, java.util.concurrent.TimeUnit.MINUTES)) .build(); return MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .httpClient(httpClient) .build(); } }参数说明:connectTimeout用于建立TCP连接,分片上传时服务端可能在高负载下响应变慢,30秒是合理值。readTimeout和writeTimeout设置到5分钟,是因为大分片在弱网环境下传输时间会很长,默认的几十秒超时会导致分片上传被误判失败。connectionPool的50表示最多复用50个空闲连接,这个数字要和并发线程数匹配,后面讲并发时还会提到。
3.3 分片上传核心代码:文件分片、并发提交、合并校验一次做完
下面是我的分片上传核心实现,我把它拆成比较清晰的几步:计算分片数量、按偏移量切割文件、线程池并发上传、收集ETag并排序合并。
import io.minio.MinioClient; import io.minio.PartETag; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.concurrent.*; public class MultipartUploader { private static final int DEFAULT_PART_SIZE = 16 * 1024 * 1024; // 16MB private static final int DEFAULT_CONCURRENCY = 8; private final MinioClient client; private final String bucketName; private final String objectName; public MultipartUploader(MinioClient client, String bucketName, String objectName) { this.client = client; this.bucketName = bucketName; this.objectName = objectName; } public void upload(Path filePath) throws Exception { long fileSize = Files.size(filePath); String uploadId = client.initiateMultipartUpload(bucketName, objectName); System.out.println("initiated uploadId=" + uploadId); int partCount = (int) ((fileSize + DEFAULT_PART_SIZE - 1) / DEFAULT_PART_SIZE); ExecutorService pool = Executors.newFixedThreadPool(DEFAULT_CONCURRENCY); List<Future<PartETag>> futures = new ArrayList<>(); for (int i = 1; i <= partCount; i++) { long offset = (long) (i - 1) * DEFAULT_PART_SIZE; long partSize = Math.min(DEFAULT_PART_SIZE, fileSize - offset); futures.add(pool.submit(() -> { try (InputStream in = Files.newInputStream(filePath)) { in.skip(offset); // 这里实际读取长度为partSize的流,需要额外封装限制读取长度 return client.uploadPart(bucketName, objectName, uploadId, i, in, partSize); } })); } pool.shutdown(); pool.awaitTermination(1, TimeUnit.HOURS); List<PartETag> etags = new ArrayList<>(); for (Future<PartETag> f : futures) { etags.add(f.get()); } etags.sort(Comparator.comparingInt(PartETag::partNumber)); client.completeMultipartUpload(bucketName, objectName, uploadId, etags); System.out.println("upload completed, object=" + objectName); } }逻辑说明:这段代码的核心思路是先把文件切分成固定大小的逻辑分片,然后通过fixedThreadPool并发上传各自的偏移量区间。每个任务内部重新打开文件的输入流,并用skip跳到对应的起始偏移量,这样做比在循环外共享一个InputStream要安全得多——共享流会让多线程间的文件指针相互干扰,实测中会出现数据错乱、合并后文件损坏的问题。
有两个地方需要特别注意。第一个是InputStream的读取长度,上面的代码直接传了partSize给uploadPart,SDK内部会按这个长度读取,但有些版本的SDK行为略有差异,稳妥做法是用BoundedInputStream把读入数据的长度限制在partSize以内,防止多读或少读。第二个是Future.get()的顺序和合并排序没有直接关系,即使Future返回顺序是乱序的,只要在合并前按partNumber排序就没有问题。
如果分片数量很大,不建议把所有Future一次性存进List,而是每提交一批就take一批结果,保持内存中有界。否则10000个分片就会有10000个Future对象挂在内存里,GC压力很大。
3.4 合并与校验:合并失败的常见场景
合并分片是整套流程里最少出错、但一旦出错最让人头疼的环节,因为错误信息往往很模糊。最典型的失败是“Part number must be lte 10000”或者ETag列表不完整。
我的合并代码会在提交前做一个校验:确认etags列表的长度等于分片总数,且partNumber是从1开始连续递增的。这两个条件任何一个不满足,都不应该发合并请求,直接日志记录并返回失败即可。
// 合并前校验:长度和连续性 if (etags.size() != partCount) { throw new IllegalStateException("part count mismatch: expected " + partCount + " but got " + etags.size()); } for (int i = 1; i <= etags.size(); i++) { if (etags.get(i - 1).partNumber() != i) { throw new IllegalStateException("part number gap at index " + i); } } client.completeMultipartUpload(bucketName, objectName, uploadId, etags);这段校验逻辑在正常路径下不会触发,但在分片上传任务被中断、部分分片上传结果丢失后,它能帮你快速定位问题出在收集阶段还是合并阶段,省下翻日志的时间。
4. 断点续传:把uploadId和已传分片列表落盘才是关键
4.1 续传的核心思路:初始化时先看一眼“未完成的上传任务”
分片上传给了断点续传一个天然的基础:上传中断时,服务端还保留着uploadId和已经成功上传的分片数据。只要我们能找回这些信息,就能跳过已经传完的分片,只传剩下的部分。这个能力对应MinIO的listParts接口和上传任务的查询接口。
找回已上传分片有两种方式。第一种是运行时内存中维护状态,进程不退出时有效;第二种是把uploadId和已传分片信息持久化到本地文件或数据库,进程重启后也能恢复。强烈建议用第二种,因为线程池里的Future在JVM崩溃后什么都没留下来,但服务端的orphan分片还占着存储空间。
具体流程是:上传任务开始前,先检查本地是否存有目标对象的检查点记录;如果有,直接用记录的uploadId查询服务端分片列表;如果没有,就发起initiateMultipartUpload创建新任务。这个“先查再建,建完即存”的时序不能乱。
4.2 检查点文件:记录uploadId和已传分片的最小数据结构
我的检查点文件用JSON格式,包含对象名、uploadId、分片大小、已成功上传的分片列表。代码里的对应结构大致如下:
import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class UploadCheckpoint { private String bucketName; private String objectName; private String uploadId; private int partSize; private Map<Integer, String> uploadedParts; // partNumber -> etag public UploadCheckpoint(String bucketName, String objectName, String uploadId, int partSize) { this.bucketName = bucketName; this.objectName = objectName; this.uploadId = uploadId; this.partSize = partSize; this.uploadedParts = new ConcurrentHashMap<>(); } public void markPartUploaded(int partNumber, String etag) { uploadedParts.put(partNumber, etag); } public boolean isPartUploaded(int partNumber) { return uploadedParts.containsKey(partNumber); } // getter/setter 省略 }检查点的持久化很简单,每次partNumber上传成功后就重写一次JSON文件。这里不用纠结IO性能,因为分片数量最多也就几百个,写入一次几字节到几KB的数据,对上传总耗时来说可以忽略不计。关键是不能只在上传全部结束时才写文件,那样的话中断就什么都恢复不了。
还需要考虑检查点文件的更新时机。我习惯在每次分片上传成功后立即更新文件和内存状态,然后才提交下一个分片任务。这样做比先并发跑完再统一记录要稳妥——并发环境下如果最后一步进程崩溃,前面传完的分片信息可能全部丢失。
4.3 断点续传完整实现:跳过已传分片,从listParts确认服务端状态
仅靠本地检查点文件并不完全可靠,因为可能出现本地记录了某个分片上传成功,但服务端实际没存住的情况。所以续传时,我会先调用服务端的listParts接口,以服务端返回为准,本地检查点只用来减少查询和比对的工作量。
import io.minio.ListPartsResponse; public class ResumeUploader extends MultipartUploader { public void resumeUpload(Path filePath, UploadCheckpoint checkpoint) throws Exception { ListPartsResponse partList = client.listParts(bucketName, objectName, checkpoint.getUploadId()); Map<Integer, String> serverParts = new ConcurrentHashMap<>(); partList.result().partList().forEach(p -> serverParts.put(p.partNumber(), p.etag())); long fileSize = Files.size(filePath); int partCount = (int) ((fileSize + checkpoint.getPartSize() - 1) / checkpoint.getPartSize()); ExecutorService pool = Executors.newFixedThreadPool(DEFAULT_CONCURRENCY); List<Future<PartETag>> futures = new ArrayList<>(); for (int i = 1; i <= partCount; i++) { final int partNumber = i; if (serverParts.containsKey(partNumber)) { if (checkpoint.isPartUploaded(partNumber)) { // 服务端和本地都认为已上传,跳过 continue; } } futures.add(pool.submit(() -> uploadSinglePart(filePath, checkpoint, partNumber))); } pool.shutdown(); pool.awaitTermination(1, TimeUnit.HOURS); List<PartETag> etags = collectEtags(checkpoint, partCount); etags.sort(Comparator.comparingInt(PartETag::partNumber)); client.completeMultipartUpload(bucketName, objectName, checkpoint.getUploadId(), etags); } }逻辑说明:这里把“服务端已上传”作为跳过的唯一标准,本地检查点只作为辅助判断。如果服务端有某个分片但本地检查点没有,说明上次运行时本地记录没来得及落盘,这时直接复用服务端的ETag即可。反过来,如果本地有记录但服务端查不到,说明该分片要么没传成功要么已过期,必须重新上传。
上传中断恢复时还有一批分片是“未完成但属于本次任务”的,它们会以未完成Multipart Upload的形态占着服务端的存储空间,暂时不影响使用,但长期堆积会占用空间,需要定期清理。这里补充一个可选的策略:如果断点时间超过某个阈值(比如一天),干脆放弃旧的uploadId,重新初始化上传任务,同时清理不完整的旧任务。
4.4 服务端过期清理:orphan分片怎么回收
断点续传的时间跨度可能很长,服务端的未完成上传任务默认不会自动消失。MinIO与S3协议一致,提供了专门的接口来列举和终止未完成的分片上传任务。定期执行清理是这个方案的必修课,否则每次忘记收拾,桶里就会堆满看不见的碎片数据。
import io.minio.ListMultipartUploadsResponse; import io.minio.RemoveUploadResult; // 列出所有未完成的上传任务 ListMultipartUploadsResponse res = client.listMultipartUploads(bucketName); res.result().uploadList().forEach(upload -> { // 根据上传时间判断是否超期,超期则终止任务并释放分片 client.removeUpload(bucketName, objectName, upload.uploadId()); });清理策略可以做成一个定时任务,每天凌晨跑一次,终止那些创建时间超过48小时仍未合并的上传任务。这里有个经验:很多同学刚上手时只实现了断点续传,却忘了清理机制,结果桶的空间莫名其妙变小,去控制台看了半天也找不到原因,其实是未完成的分片把空间吃掉了。
5. 并发、超时、校验:调优细节与六个高频踩坑记录
5.1 并发线程数与分片大小的匹配关系
并发线程数不是越大越好。分片上传的瓶颈通常不在客户端CPU,而在于网络带宽和服务端的吞吐能力。我做过一次实测:同一个900MB文件,分片大小16MB,并发从4调到16,总耗时从35秒降到18秒,但并发再往上调到32时,耗时反而变成了22秒。原因是个别分片请求在服务端排队,加上客户端内存占用升高,GC变频繁。
所以推荐的做法是:并发线程数先按CPU核心数设置,然后在测试环境逐步调大,找到耗时曲线的拐点。大批量分片(100片以上)时,还要注意连接池的可用连接数,至少要大于并发线程数,否则线程会在获取连接时阻塞。
5.2 踩坑记录:上传中断后想当然地重传,结果文件永不完整
现象:断网后客户端重试整个上传流程,代码看起来没有任何问题,上传也提示完成了,但下载到本地打开文件,提示文件损坏或截断。
原因:一开始客户端用了一次initiateMultipartUpload后没有保存uploadId,重试时又发起了一次新的uploadId,旧的上传任务里已经传好的分片孤零零留在服务端,但新任务里分片不齐就提交了合并。合并时服务端要求所有分片都在,于是补传了一部分小分片,最终文件虽然合并出来了,但顺序和内容不对。
解决:重试之前先从服务端listParts确认已有的分片,再决定是继续旧的uploadId还是重新初始化。绝不能每次重试都从initiateMultipartUpload开始。
5.3 踩坑记录:分片上传100%后卡在合并,几分钟都不返回
现象:所有分片都显示上传成功了,日志里print了“part upload done”,但completeMultipartUpload调用一直不返回,最后抛出超时异常。
原因:合并请求会把所有分片的元数据发给服务端做组合,MinIO底层需要将各个分片的数据进行拼接或重组。分片数量超过几百个时,这个操作耗时明显上升。很多人用readTimeout默认的几十秒,合并必然超时。另外,有些网络代理或网关会限制请求体的大小,PartETag列表很长时请求体也会很大,被网关拦截就会表现为卡住后超时。
解决:把客户端的readTimeout调大到5分钟;如果分片超过1000个,考虑增大分片大小以减少分片数量。还要检查前面的代理层对请求体的限制,必要时放行大请求体。
5.4 踩坑记录:并发上传时,随机出现“InternalError”或“ChecksumMismatch”
现象:并发数调高后,偶尔某个分片上传报InternalError或ETag不匹配,单独重新上传该分片又成功了。
原因:并发环境下,多个分片请求共用同一个InputStream实例,流内部的文件指针是共享的,线程A读取的位置会被线程B的skip影响,导致某个分片读到了错误的数据范围。
解决:每个分片的上传任务必须使用独立的InputStream,并且从文件的开头重新打开流,再skip到目标偏移量。这个做法会多消耗一点打开文件的系统调用,但换来的是数据准确性,完全值得。
5.5 踩坑记录:断网重连后,本地检查点文件损坏或消失
现象:进程被强杀或机器重启后,检查点文件变成了空文件,或者JSON格式损坏,导致无法恢复上传任务。
原因:检查点文件采用了“直接写原文件”的策略,写入过程中机器断电,文件就处于半写状态。
解决:写入检查点时,先写一个临时文件,然后使用原子重命名覆盖原文件。这样要么读到旧版本完整文件,要么读到新版本完整文件,不会出现中间状态。代码上就是:
Path tmp = Paths.get(checkpointPath + ".tmp"); Files.write(tmp, json.getBytes(StandardCharsets.UTF_8)); Files.move(tmp, Paths.get(checkpointPath), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);5.6 踩坑记录:大文件上传时的内存颠簸
现象:上传1GB以上的文件时,客户端JVM内存占用一路上涨,甚至触发OutOfMemoryError。
原因:代码里把所有分片数据一次性读入了内存,或者把所有Future和ETag对象无限制地堆积在列表里。
解决:分片时不要整体读入内存,用流的姿势逐个分片传输;Future也要有界地收集,每完成一个就移除一个。检查点文件合并时分散读取,不要一次性把整个文件装载到Heap里。
6. 推进生产级上传:预签名URL、服务端事件通知与压测建议
分片上传和断点续传在Java后端跑通之后,离生产环境还差几个环节。第一个是预签名URL配合分片使用。如果你的架构里Java后端只负责签发上传凭证,实际文件流从客户端或浏览器直接上传到MinIO,那就要用createPresignedPostUrl或预签名PUT URL,把分片上传的权限临时授予给前端。这样做能减轻后端带宽压力,否则所有文件数据都要经过应用服务器中转,流量大了就会成为瓶颈。
// 生成一个预签名URL,有效期7天,用于前端直传大对象 String presignedUrl = minioClient.getPresignedObjectUrl( io.minio.GetPresignedObjectUrlArgs.builder() .method(io.minio.http.Method.PUT) .bucket(bucketName) .object(objectName) .expiry(7 * 24 * 3600) .build());第二个是服务端事件通知。合并完成后,MinIO可以向配置好的消息队列发一个事件,Java后端监听这个事件做后续处理,比如更新数据库状态、触发视频转码。事件通知的好处是解耦,上传流程不用在合并请求后同步等待其他服务的处理结果。
第三个是压测方法。我习惯用同一个固定文件(例如500MB或1GB大小),分别用不同的分片大小和并发数跑5轮,记录每轮的总耗时、合并耗时、失败分片数,做成一个对比表格。其中合并耗时单独记录很重要,因为合并是串行操作,无法通过增加并发优化。压测时还要把客户端的GC日志打开,留意Full GC的频次,它会影响上传线程的执行节奏。
我的一个保留习惯是:每次修改分片大小或并发参数后,只对同一个测试文件跑对比,不同文件类型和大小混着测会导致数据说服力不足。另外,所有压测都在非业务高峰期做,避免影响线上的正常读写。
参数调优没有一步到位的银弹,每换一个网络环境、每换一个MinIO服务端版本,之前测出来的最优参数都可能不再适用。我的建议是:把分片大小、并发线程数、超时时间做成配置项放出来,随环境调整,而不是写死在代码里。上线前至少做一次模拟弱网的测试,人为断掉网络,观察断点续传恢复的耗时和正确性。这套方案帮我在多个项目里把大文件上传的失败率从百分之几降到了千分之一以下,希望帮到你。
本文还有配套的精品资源,点击获取