MinIo 最佳分片上传PLUS

14,015 阅读4分钟

第一次接触到MinIo是在去年,在这之前我们项目上用的是FastDFS,这个东西有多难用相信大家都知道。

后来发现了MinIo后,对这个轻便的对象存储产生了兴趣。因此特别研究了一下Minio的分片上传是如何实现的,又是怎样一个流程。因为去年比较空闲,对Minio的分片上传有过研究,于是出过一篇关于Minio分片上传的博客

分片上传有啥好研究的?
相信接触过Minio的小伙伴清楚的知道,minio在上传文件的api中实现了分片上传的功能,当文件大于5m时,会自动采用分片的方式进行文件上传。
但这有一些不完美的地方;
    1.分片上传我们无法得知上传的分片后的序号,也就是说,每上传一个分片,我们都需要自己去记录已上传分片的序号(假如需要自己纪录分片文件信息的话)。这将导致一个文件分片5个,那么同样还需要调用5次后端接口去记录这5个分片的信息。这无疑大大浪费了性能,且无法做到并发上传。
    2.上传流程是 前端->后端->Minio Server。也就是说,是前端上传文件到后端,再由后端上传到minio。这跟我们用阿里云那些厂商的对象存储好像不太一样啊~因为我们对于阿里云来说,我们的后端程序就是阿里云对象存储的前端,而他们的分片上传流程是 前端->对象存储服务
    3.上传流程拉长,带宽损失大
    
那如何解决上边的问题?
去年研究的时候,有出过一篇博客,大家可以看看:[Minio最佳分片上传](https://blog.csdn.net/anxyh_name/article/details/108397774)

全文完。

哈哈哈,开玩笑的,上述博客其实已经解决了前边提到过的问题,但是后来发现使用博客中的办法去实现的时候,在最后的文件合并操作时,非常慢。难以忍受。
有小伙伴上8h16g的服务器测试过,5个G文件的合并耗时将近9分钟。

为什么会这样呢,主要还是minio不“认”我们上传的分片文件是它所“定义”的分片文件,它只是觉得你调用了合并文件api,需要将若干个文件合并成一个,仅此而已。
所以无法走minio自己理解的合并方式
验证这个问题很简单,上述博客中上传的分片文件,在bucket里是能看见的,能浏览的,而minio自己定义的分片文件,是看不见的,包括在磁盘中去看,也是看不到的。

所以现在主要要解决的其实是合并文件太慢的问题。
SDK源码中关键的API
createMultipartUpload //创建分片上传,返回uploadId
getPresignedObjectUrl //创建文件预上传地址
listParts //获取uploadId下的所有分片文件
completeMultipartUpload //合并分片文件

看到这4个方法,相信有的小伙伴已经知道这些东西似乎可以组成一个像厂商们的对象存储那样的上传流程了
这些方法在SDK中不是公开的,需要继承MinioClient,把这几个方法变成公开的。
实现思路
1.前端调用后端createMultipartUpload获取uploadId
@SneakyThrows
public String getUploadId(String bucketName, String region, String objectName, Multimap<String, String> headers,
						  Multimap<String, String> extraQueryParams) {
	CreateMultipartUploadResponse response = createMultipartUpload(bucketName, region, objectName, headers,
			extraQueryParams);

	return response.result().uploadId();
}
2.前端计算欲上传文件的分片总数量,并调用后端getPresignedObjectUrl生成对应数量的欲上传地址

@RequestMapping("getUploadPath")
public List<String> getUploadPath(@RequestBody UploadDto uploadDto) {
	List<String> urlList = new ArrayList<>();
	for (int i = 1; i <= uploadDto.getChunkCount(); i++) {
		String url = minIoUtil.createUploadUrl(uploadDto.getBucketName(), uploadDto.getFileName(), i, uploadDto.getUploadId());
		urlList.add(url);
	}
	log.info("urlList:{}", urlList);
	return urlList;
}

@SneakyThrows
public String createUploadUrl(String bucketName,String objectName,Integer partNumber, String uploadId){
	return myMinioClient.getPresignedObjectUrl(
			GetPresignedObjectUrlArgs.builder()
					.method(Method.PUT)
					.bucket(bucketName)
					.object(objectName)
					.expiry(604800)
					.extraQueryParams(
							newMultimap("partNumber", Integer.toString(partNumber), "uploadId", uploadId)
					)
					.build()
	);
}
3.前端通过欲上传地址上传完所有的分片文件后,调用后端completeMultipartUpload合并文件
@RequestMapping("completeFile")
public void completeMultipartUpload(@RequestBody UploadDto uploadDto) {
	ListPartsResponse listPartsResponse = minIoUtil.listParts(uploadDto.getBucketName(),
			uploadDto.getFileName(),
			uploadDto.getUploadId());
	List<Part> parts = listPartsResponse.result().partList();
	minIoUtil.completeMultipartUpload(uploadDto.getBucketName(),
			uploadDto.getFileName(),
			uploadDto.getUploadId(),
			parts.toArray(new Part[]{}));
}
备注
通过上述的流程,就可以实现前端上传文件时直连Minio,而后端程序,只需要做一些简单的事情,比如返回uploadId,调用api合并文件以及一些自己的业务逻辑等等。
自己测试过,这样流程跑通是没问题的。同时经过一个go小伙伴用 Go SDK 类似的测试,5个G的文件合并耗时好像是1分钟不到还是几十秒来着,记不清了。莫怪莫怪,所以叫做不严谨PLUS嘛

有时间的话再上一个前后端的完整demo,就先这样啦

2021-12-10 16:24:20

经过严谨测试,合并文件速度非常之快,750m的文件合并几乎在1秒内。

微信截图_20211210161939.png

微信截图_20211210162000.png

微信截图_20211210162142.png

微信截图_20211210162242.png

等把Demo完善一下就放上来