Spring Boot MinIO
# 简介
MinIO 是一个基于 Apache License v2.0 开源协议的对象存储服务。它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器 / 虚拟机镜像等,而一个对象文件可以是任意大小,从几 kb 到最大 5T 不等。
# 环境
ip | 端口 |
---|---|
100 | 9000 |
102 | 9000 |
我这里服务器就只用了两台,会用来做 MinIO 的分布式文件。
# 单机
wget https://dl.min.io/server/minio/release/linux-amd64/minio
# 赋予执行权限
chmod +x minio
# 启动 并设置数据存储位置(单机)
./minio server /data
1
2
3
4
5
2
3
4
5
# 分布式
建议编写脚本如下 start.sh
# 端口打开 9000
/sbin/iptables -I INPUT -p tcp --dport 9000 -j ACCEPT
# 重新设置密钥
export MINIO_ACCESS_KEY=minio
export MINIO_SECRET_KEY=minio@2020
# 启动并指定分布式服务器,这里会有坑 可以看 5 标题
./minio server \
http://10.240.30.100/home/MinIO/data{1...4} http://10.240.30.102/home/MinIO/data{1...4}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
{1...4} 的意思是会在 /home/MinIO/ 生产 4 个文件 data1 data2 data3 data3,其中 "..." 必须为 3 个点,得以获得最佳的纠删码分布。启动成功后会有一个 VIP 做为网关。我自己使用 VIP 一旦崩溃,无法转移,具体原因尚不清楚。
纠删码 是一种恢复丢失和损坏数据的数学算法,来保护数据免受硬件故障和无声数据损坏。 即便您丢失一半数量的硬盘,您仍然可以恢复数据。内部会根据你提供的磁盘数量或节点数量,去分配数据和奇偶校验块,一般是对半开,确保对硬盘故障提供最佳保护。Minio 纠删码的设计目标是为了性能和尽可能的使用硬件加速。
#五、分布式出现的问题
我很疑惑他是怎么知道我的 nginx1.18.0,我并没有给 MinIO 配置过相关 nginx 信息。
API: SYSTEM()
Time: 17:53:35 CST 12/25/2020
DeploymentID: e660d5ad-c358-4aca-bdb5-f9859348b8c6
Error: <html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.18.0</center>
</body>
</html>
(*errors.errorString)
2: cmd/notification.go:468:cmd.(*NotificationSys).updateBloomFilter.func1()
1: pkg/sync/errgroup/errgroup.go:55:errgroup.(*Group).Go.func1()
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 改到如下配置即可解决
/sbin/iptables -I INPUT -p tcp --dport 9000 -j ACCEPT
export MINIO_ACCESS_KEY=minio
export MINIO_SECRET_KEY=minio@2020
./minio server \
http://10.240.30.102/home/MinIO/data1 http://10.240.30.100/home/MinIO/data1 \
http://10.240.30.102/home/MinIO/data2 http://10.240.30.100/home/MinIO/data2
1
2
3
4
5
6
2
3
4
5
6
之前我设置的是每个节点会创建 4 个目录,现在改为 2 个目录,并没有在报错。在官网看到如下:
MinIO 选择最大的 EC (纠删码) 集大小,该大小划分为驱动器总数或给定的节点总数 - 确保保持统一分布,即每个节点每集合参与相等数量的驱动器。
我理解的意思是,在分布式中可能单个节点的磁盘数量根据分布式的节点数量而定。
# 特性及注意
- 在分布式中,如果有 16 个节点,其中 8 个节点蹦了,依然可以查看,但无法编辑或上传,只有在可用节点为 9 台才能正常使用。
- 单独给分布式其中的某一个节点上传文件,文件会被同步到其他节点。
- 分布式 MinIO 所需的最小磁盘 (目录) 为 4,所以小于 4 就会报错,4 是所有节点的磁盘 (目录) 之和,不是单个节点需要设置 4 个磁盘 (目录),并且使用分布式 Minio 自动引入了纠删码功能。
- 2 台分布式的时候,会分配一个主的做为网关 (VIP),当 VIP 被宕,无法在通过 VIP 访问到。
- 分布式中密钥必须一致。
- 2 台分布式的时候,其中一台挂掉另一台无法使用任何功能。
- minio 自带控制台 http://xx.xxx.xxx.xx:9000/minio/login 老是会忘记
- 新建的 bucket 上传文件后是没办法查看到的,需要修改 bucket 读写权限后重新上传。
# 整合 spring boot
依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.0.3</version>
</dependency>
1
2
3
4
5
2
3
4
5
控制层
package com.giant.cloud.controller;
import com.giant.cloud.service.ImgServer;
import com.giant.security.util.ResponseData;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
/**
* @Author big uncle
* @Date 2020/6/15 10:41
**/
@RestController
@RequestMapping("/img")
@RefreshScope
public class ImgController {
@Resource(name = "MinIOServer")
private ImgServer imgServer;
@PostMapping("/push")
public ResponseData push(@RequestParam("file") MultipartFile file,String path,String bucket){
return imgServer.push(bucket,path,file);
}
@PostMapping("/del")
public ResponseData<Boolean> del() {
return imgServer.del();
}
@PostMapping("/get")
public ResponseData get() {
return imgServer.download();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
服务层
package com.giant.cloud.service.impl;
import com.giant.cloud.code.UploadFileCode;
import com.giant.cloud.config.FileEnvironmentConfig;
import com.giant.cloud.service.ImgServer;
import com.giant.security.util.ResponseData;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.UUID;
/**
* @author big uncle
* @date 2021/1/11 17:50
* @module
**/
@Service(value = "MinIOServer")
public class MinIOServerImpl implements ImgServer {
@Resource
private ObjectServerImpl objectServer;
@Resource
private FileEnvironmentConfig fileEnvironmentConfig;
@Override
public ResponseData<String> push(String bucket,String path,MultipartFile file) {
try {
Long milliSecond = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
String id = UUID.randomUUID().toString().replaceAll("-","");
String fileSuffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
String objName = id + milliSecond + fileSuffix;
String str = objectServer.createObject(bucket,path+objName, file.getInputStream());
return ResponseData.successResponse(fileEnvironmentConfig.getGateway()+bucket+"/"+str);
}catch(Exception e){
e.printStackTrace();
return ResponseData.failureResponse(UploadFileCode.UPLOAD_FILE_CODE_1001);
}
}
@Override
public ResponseData<String> download() {
return null;
}
@Override
public ResponseData<Boolean> del() {
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
ObjectServerImpl
package com.giant.cloud.service.impl;
import cn.hutool.core.util.StrUtil;
import com.giant.cloud.code.BucketCode;
import com.giant.cloud.service.ObjectServer;
import com.giant.security.exp.impl.BasicException;
import io.minio.MinioClient;
import io.minio.ObjectWriteArgs;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.InputStream;
/**
* @author big uncle
* @date 2021/1/11 18:39
* @module
**/
@Service
public class ObjectServerImpl implements ObjectServer {
@Resource
private MinioClient minioClient;
@Resource
private BucketServerImpl bucketServer;
@Override
public String putObject(String bucket, String filePath, InputStream inputStream) throws Exception{
if(StrUtil.isBlank(bucket)){
throw new BasicException(BucketCode.BUCKET_CODE_200);
}
if(!bucketServer.bucketExists(bucket)){
throw new BasicException(BucketCode.BUCKET_CODE_201);
}
minioClient.putObject(
PutObjectArgs.builder().bucket(bucket).object(filePath).stream(
inputStream, -1, ObjectWriteArgs.MIN_MULTIPART_SIZE)
.contentType("image/png")
.build());
return filePath;
}
@Override
public String createObject(String bucket,String path, InputStream inputStream) throws Exception {
if(!bucketServer.bucketExists(bucket)){
bucketServer.createBucket(bucket);
}
return putObject(bucket,path,inputStream);
}
@Override
public void delObject(String bucket,String path,String name) throws Exception {
minioClient.removeObject(
RemoveObjectArgs.builder().bucket(bucket).object(path+name).build());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
上次更新: 4/1/2025, 5:03:02 PM