Springboot整合MinIO

一、Minio基础知识

官方说明

Minio是一款高性能的对象存储服务器,它兼容Amazon S3 API。它的设计目的是为了提供云存储服务的性能和可扩展性,同时还保持着本地存储的简单性和易用性。Minio可以在Linux、MacOS和Windows等操作系统上运行,它可以通过命令行界面或RESTful API进行管理。

Minio的核心是对象存储,对象是一组二进制数据和元数据的组合。对象可以存储为文件,也可以存储为内存中的数据结构。对象可以存储在不同的存储介质中,如本地磁盘、网络文件系统、云存储等。Minio支持多种存储介质,它可以轻松地将数据存储到本地磁盘、Amazon S3、Google Cloud Storage、Microsoft Azure Blob Storage等云存储服务中。

Minio的架构采用了分布式的设计,它可以将数据分散存储在多个节点中,从而实现数据的高可用和容错性。在Minio中,节点被称为Minio Server,每个Minio Server可以存储一个或多个对象存储桶。对象存储桶是一组对象的集合,类似于文件系统中的文件夹。每个对象存储桶都有一个唯一的名称,它可以在Minio集群中全局唯一。

Minio的数据访问是通过RESTful API实现的,它可以提供各种数据管理功能,如创建、删除、读取、写入对象等。Minio的API与Amazon S3 API兼容,这意味着您可以使用Amazon S3 SDK和工具来与Minio交互。

MinIO是一款基于Go语言发开的高性能、分布式的对象存储系统。客户端支持Java,Net,Python,Javacript, Golang语言。

MinIO 英文官网 MinIO 中文官网 注意:中文官方更新不及时,会有很多坑,请以英文官网为准。

基础概念

1、Object:存储到minio的基本对象,如文件,字节流,Anything。。。。

2、Bucket:用来存储Object的逻辑空间。每个Bucket之间的数据是互相隔离的。对于客户端而言,就相当于存放文件的顶层文件夹。

3、Drlve:存储数据的磁盘,在MinIO启动时,以参数的方式传入。MinIO中所有的对象数据都会存在Drive里。

4、Set:即一组 Drive的集合,分布式部署根据集群规模自动划分一个或者多个Set,每个Set中的Drive 分布在不同位置。一个对象存储在一个Set上。

4.1、一个对象存储在一个Set上。

4.2、一个集群划分为多个Set。

4.3、一个Set包含的Drive数量是固定的,默认由系统根据集群规模自动计算得出。

4.4、一个Set中的drive 尽可能分布在不同的节点上。

5、纠删码:Minio 使用纠删码机制来保证高可靠性。

5.1、使用highwayhash来处理数据损坏(Bit Rot Protectio)。关于纠删码,简单来说就是可以通过数学计算,把丢失的数据进行还原。

5.2、纠删码是恢复丢失和算怀数据的数学算法,minio采用reed-solomincode将对象拆分成N/2数据和N/2奇偶校验块。这就意味着如果是12块盘,一个对象会被分成6个数据快、6个奇偶校验块,你可以丢失任意6块盘(不管其存放的是数据块还是奇偶校验块),你任可以从剩下的盘中的数据进行恢复。

5.2、纠删码模式,把数据分成 DATA BLOCk:数据块、 PARITY BLOCK:校验块。一个BLOCK 10M左右,如果一个文件小于10M,就以文件大小进行分块。如果一个文件大于10m,就以10M为一个文件单位进行分块。

应用场景

1、互联网非结构化数据的存储需求

1.1 、电商网站:海量商品图片

2.1、视频网站:海量视频文件

3.1、网盘:海量文件

MinIO优势

1、数据保护

1.1、分布式MInio采用纠删码来放反多个节点待机和位衰减 bit rot

1.2、分布式Minio至少需要4个硬盘,使用分布式Minio自动引入了纠删码功能。

2、高可用

2.1、单机Minio服务存在单点故障,相反,如果是有一个N快硬盘的分布式Minio,只要有N/2硬盘在线,你的数据就是安全的。不过你需要至少有N/2+1个硬盘来创建新的对象。

2.2、例如,一个16节点的Minio集群,每个节点16块块硬盘,计算8台服务器宕机,这个集群仍然是可读的,不过你需要9台服务器才能写入数据。

3、一致性

3.1、Minio在分布式和单机模式下,所有读写炒作都严格遵守read-after-write 一致性模型

二、安装MinIO(CentOS7)

1
sudo yum install wget
1
wget https://dl.minio.org.cn/server/minio/release/linux-amd64/minio
1
mkdir /usr/local/minio
1
cp minio /usr/local/minio/
1
cd /usr/local/minio/
1
chmod +x minio
1
vim start-minio.sh
1
2
3
4
5
6
export MINIO_ROOT_USER=root
export MINIO_ROOT_PASSWORD=cqc123456
# ./minio server /usr/local/minio/data > /usr/local/minio/minio.log 2>&1 & 主要:密码要8位

# 这个使新版的,把后台管理设置为9111端口,api接口和访问默认还是9000
./minio server --console-address 0.0.0.0:9111 /usr/local/minio/data > /usr/local/minio/minio.log 2>&1 &

三、Springboot整合

1.先去管理页面创建Buckets(桶)

image-20231010141050675

2.导入依赖

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
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!--MinIO-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.6</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>

3.项目配置(application.properties)

1
2
3
4
5
6
7
8
9
10
11
12

server.port=9091

# 配置minio相关
minio.endpoint = http://192.168.55.180:9000
minio.accessKey = root
minio.secretKey = cqc123456
minio.bucketName = cqc

# 解决大文件上传
spring.servlet.multipart.max-file-size=1000MB
spring.servlet.multipart.max-request-size=5000MB

4.代码部分

ResponseData.java

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
package com.cactus.minio.common;

public class ResponseData {

private int code;
private String msg;
private Object data;

public static ResponseData error(String msg) {
return new ResponseData(1500,msg,null);
}

public ResponseData(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}

public static ResponseData success(Object urlList) {
return new ResponseData(200,"success",urlList);
}

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public Object getData() {
return data;
}

public void setData(Object data) {
this.data = data;
}
}

MinioConfig.java

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
package com.cactus.minio.config;

import io.minio.MinioClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "minio")
public class MinioConfig{
private String endpoint;
private String accessKey;
private String secretKey;
private String bucketName;

/**
* 注入minio 客户端
* @return MinioClient
*/
@Bean
public MinioClient minioClient(){

return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}

FileController.java

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package com.cactus.minio.controller;

import com.cactus.minio.common.ResponseData;
import io.minio.*;
import io.minio.http.Method;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/file")
public class FileController {
@Resource
private MinioClient minioClient;

@Value("${minio.bucketName}")
private String bucketName;

/**
* 文件上传
* @param file
* @return ResponseData
*/
@PostMapping("/upload")
public ResponseData upload(@RequestParam(name = "file") MultipartFile[] file){
if(file == null || file.length ==0){
return ResponseData.error("上传文件不能为空");
}
//防御性编程,批量限制少于10个
if(file.length > 10){
return ResponseData.error("请上传少于" + file.length + "个文件");
}
try {
List<String> urlList = new ArrayList<>(file.length);
for (MultipartFile multipartFile : file){
InputStream in = multipartFile.getInputStream();
// minioClient.statObject(StatObjectArgs.builder().object(file.getOriginalFilename()).bucket(bucketName).build());
minioClient.putObject(PutObjectArgs.builder().object(multipartFile.getOriginalFilename())
.bucket(bucketName)
.contentType(multipartFile.getContentType())
.stream(in, multipartFile.getSize(),-1).build());
in.close();
String str = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.bucket(bucketName)
.object(multipartFile.getOriginalFilename())
.method(Method.POST)
.build());
String urlStr = StringUtils.substringBefore(str, "?");
urlList.add(urlStr);
}
Map<String, Object> map = new HashMap<>();
map.put("urlList", urlList);
return ResponseData.success(map.get("urlList"));
} catch (Exception e) {
return ResponseData.error("上传失败:" + e.getMessage());
}
}

/**
* 下载文件
* @param filename
* @param response
*/
@GetMapping("/download/{filename}")
public void download(@PathVariable String filename, HttpServletResponse response){
InputStream in = null;
try {
//获取对象信息
StatObjectResponse stat = minioClient.statObject(
StatObjectArgs.builder().bucket(bucketName).object(filename).build());
response.setContentType(stat.contentType());
response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(filename, "utf-8"));
//文件下载
in = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(filename).build());
IOUtils.copy(in, response.getOutputStream());
}catch (Exception e){
e.getMessage();
}finally {
if(in != null){
try {
in.close();
}catch (IOException e){
e.getMessage();
}
}
}
}

/**
* 删除文件
* @param filename
* @return ResponseData
*/
@DeleteMapping("/delete/{filename}")
public ResponseData delete(@PathVariable("filename") String filename){
try {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName)
.object(filename).build());
}catch (Exception e){
e.getMessage();
return ResponseData.error("删除失败");
}
return ResponseData.success("删除成功");
}
}

四、测试

1.上传文件

1
POST   http://localhost:9091/file/upload

image-20231010143531816

2.下载文件

1
GET http://localhost:9091/file/download/增强功力.pdf

3.删除文件

1
DELETE http://localhost:9091/file/delete/增强功力.pdf

image-20231010144016893

五、总结

  • MinIO用Buckets的概念进行分区,找一个文件 需要Buckets+文件名
  • 个人感觉MinIO比Hadoop、FastDFS无论在安装还行运维还是整合方面都是占优的
  • 上传到MinIO文件可以通过官方api合并文件(分片合并操作),也可以创建Buckets,存在大量api

image-20231010144625213


Springboot整合MinIO
https://cai-qichang.github.io/2023/10/10/Springboot整合MinIO/
作者
caiqichang
发布于
2023年10月10日
许可协议
BY-蔡奇倡