Hi,大家好,我是抢老婆酸奶的小肥仔。
在日常开发中,我们常常需要文件上传,传统上实现上传是直接将文件保存到本地磁盘,然后通过磁盘路径进行下载查看。这样子会造成磁盘被大量占用,同时一不小心手欠的话就会将文件删除。这时候,我们可能会想到用一些文件存储系统,例如:我们熟悉的FastDFS等,我们今天介绍的主角不是FastDFS,而是Minio。
废话不多说,开整。
1、Minio简介及部署
Minio:一种分布式文件存储,具有高性能,轻量级,速度快,容错率高等特点,兼容亚马逊S3云存储服务接口,并可以作为一个独立的存储后端。
minio提供了纠删码策略,即将数据进行切分,同时计算校验块,采用Reed-Solomon code将对象拆分成N/2数据和N/2奇偶校验块,假如有8块盘,数据则被分成4个数据块,4个奇偶校验块。即使这个对象丢了4块盘,数据依然可以进行恢复,因此即使我们一不小心删除了一些盘,也不用担心数据会丢失。
1.1 Minio部署
我们以win10环境为例,进行Minio部署。
1.1.1 下载Minio
minio下载地址:https://www.minio.org.cn/download.shtml
选择windows版本进行下载,下载完成后是一个exe文件。
1.1.2 部署minio
在windows下创建一个文件夹用来放置minio执行文件,例如:我将其放在D盘下的soft/minio下。
Minio的部署可以分为:单节点单磁盘,单节点多磁盘,多节点方式。
单节点单磁盘执行执行exe文件即可,我们就说说单节点多次盘,多节点两种部署方式。
1.1.2.1 单节点多磁盘
1.1.2.1.1 创建目录
即通过一个节点进行访问,数据被保存在不同磁盘下。
我们创建4个文件夹来模拟四块不同的磁盘。如图:
1.1.2.1.2 启动minio
minio启动需要编写脚本,在存放minio.exe文件夹下创建一个minio.bat文件,文件内容如下:
@echo off
set MINIO_ROOT_USER=admin
set MINIO_ROOT_PASSWORD=12345678
cd "D:\soft\minio"
start minio.exe server --console-address ":9000" D:\soft\minio\dataOne D:\soft\minio\dataTwo D:\soft\minio\dataThree D:\soft\minio\dataFour
--console-address ":9000"
:管理页的ip和端口,缺省时默认是127.0.0.1:9000
--address "127.0.0.1:9090"
:代表接口的ip和端口
1.1.2.1.3 访问
直接在浏览器上输入:http://localhost:9000即可进行访问。
1.1.2.2 多节点
即端口不一样,数据磁盘路径一致。
例如我们设置四个不同端口:9001,9002,9003,9004。分别执行如下脚本:
set MINIO_ROOT_USER=admin
set MINIO_ROOT_PASSWORD=12345678
start minio.exe server --console-address "127.0.0.1:9001" --address "127.0.0.1:9091"
http://127.0.0.1:9091/D:\soft\minio\dataOne
http://127.0.0.1:9092/D:\soft\minio\dataTwo
http://127.0.0.1:9093/D:\soft\minio\dataThree
http://127.0.0.1:9094/D:\soft\minio\dataFour
其他端口执行,只需要更改端口就好。
启动minio
由于涉及到多个端口,访问资源时,不可能对每个节点进行访问显然不合理,因此我们可以通过nginx来进行代理。配置如下:
upstream minio{
server 127.0.0.1:9001;
server 127.0.0.1:9002;
server 127.0.0.1:9003;
server 127.0.0.1:9004;
}
server{
listen 8888;
server_name 127.0.0.1;
ignore_invalid_headers off;
client_max_body_size 0;
proxy_buffering off;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_set_header Host $http_host;
proxy_connect_timeout 300;
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_ignore_client_abort on;
proxy_pass http://minio;
}
}
2、分片上传
在minio中实现上传会自动进行分块,然后再将分块上传到Minio服务器,最后在进行合并。但是我们在使用时必须要将整个文件上传到系统,然后再调用Minio接口进行文件上传,当文件比较大时,就会占用太多宽带,从而大致上传慢,甚至使服务挂掉。因此我们需要进行优化。
在minio中提供了三个方法completeMultipartUpload,createMultipartUpload,listMultipart通过这三个方法我们可以将文件进行分片,分片后返回分片上传连接,等所有分片上传完成后,再进行分片合并,从而完成整个文件的上传。大致流程:
1、用户在前端使用vue-uploader上传文件,判断文件的大小。
2、如果文件大小小于5M则直接通过接口上传到minio服务器,如果大于5M时,计算分片数,调用分片接口获取每个分片对应的上传地址。
3、根据分片计算每个分片的大小,将文件按大小进行分片,调用分片地址将分片进行上传。
4、分片上传完成后,将分片进行合并,在服务器上形成文件。
2.1 代码实现
2.1.1 后端实现
基于minio的分片上传主要是重写三个接口,即completeMultipartUpload,createMultipartUpload,listMultipart,这三个接口在minio包中是protected的,我们如果想要使用这三个方法,只能重写这三个方法即可。
completeMultipartUpload
: 即完成分片上传后,进行分片合并。
createMultipartUpload
: 即返回每个分片对应Id及上传的url。
listMultipart
: 即查询分片信息。
1、定义PearMinioClient
PearMinioClient主要是集成MinioClient,重写MinioClient中的三个方法。
/**
* @author: jiangjs
* @description: 分片上传MinioClient继承MinioClient,主要暴露:
* createMultipartUpload:创建分片请求,返回uploadId
* listMultipart:查询分片信息
* completeMultipartUpload:根据uploadId合并已上传的分片
* @date: 2023/10/24 14:08
**/
public class PearMinioClient extends MinioClient {
protected PearMinioClient(MinioClient client) {
super(client);
}
@Override
public ObjectWriteResponse completeMultipartUpload(String bucketName, String region, String objectName, String uploadId, Part[] parts, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException {
return super.completeMultipartUpload(bucketName, region, objectName, uploadId, parts, extraHeaders, extraQueryParams);
}
@Override
public CreateMultipartUploadResponse createMultipartUpload(String bucketName, String region, String objectName, Multimap<String, String> headers, Multimap<String, String> extraQueryParams) throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException {
return super.createMultipartUpload(bucketName, region, objectName, headers, extraQueryParams);
}
public ListPartsResponse listMultipart(String bucketName,String region,String objectName,Integer maxParts,Integer partNumberMarker,
String uploadId, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, IOException, InvalidKeyException, XmlParserException, InvalidResponseException, InternalException {
return super.listParts(bucketName, region, objectName, maxParts, partNumberMarker, uploadId, extraHeaders, extraQueryParams);
}
}
2、设置minio配置
获取配置文件中配置的minio相关属性,如地址等。
/**
* @author: jiangjs
* @description:
* @date: 2023/10/20 10:27
**/
@Component
@Data
@ConfigurationProperties(prefix = "minio")
public class MinioEndPointInfo {
/**
* minio节点地址
*/
private String endpoint;
/**
* 登录用户名
*/
private String accessKey;
/**
* 密码
*/
private String secretKey;
}
其中accessKey,secretKey分别在创建服务用户时创建。
创建minio配置类
/**
* @author: jiangjs
* @description: minio相关配置
* @date: 2023/10/20 10:46
**/
@Configuration
@EnableConfigurationProperties(MinioEndPointInfo.class)
public class MinioConfig {
@Resource
private MinioEndPointInfo minioEndPointInfo;
@Bean
public PearMinioClient createPearMinioClient(){
MinioClient minioClient = MinioClient.builder()
.endpoint(minioEndPointInfo.getEndpoint())
.credentials(minioEndPointInfo.getAccessKey(), minioEndPointInfo.getSecretKey())
.build();
return new PearMinioClient(minioClient);
}
}
上述minio配置类中,创建了minioClient来初始化我们定义的PearMinioClient。
3、创建minio分片工具类
/**
* @author: jiangjs
* @description: minio分片上传工具
* @date: 2023/10/30 9:34
**/
@Component
@Slf4j
public class MinioPearUploadUtil {
@Resource
private PearMinioClient pearMinioClient;
/**
* 校验当前bucket是否存在,不存在则创建
* @param bucketName 桶
*/
private void existBucket(String bucketName){
try {
boole