Minio是一个go编写基于Apache License v2.0开源协议的对象存储系统,是为海量数据存储、人工智能、大数据分析而设计,它完全兼容Amazon S3接口,十分符合存储大容量的非结构化数据从几十kb到最大5T不等。是一个小而美的开源分布式存储软件。
特点
简单、可靠:Minio采用简单可靠的集群方案,摒弃复杂的大规模的集群调度管理,减少风险与性能瓶颈,聚焦产品的核心功能,打造高可用的集群、灵活的扩展能力以及超过的性能。建立众多的中小规模、易管理的集群,支持跨数据中心将多个集群聚合成超大资源池,而非直接采用大规模、统一管理的分布式集群。
功能完善:Minio支持云原生,能与Kubernetes、Docker、Swarm编排系统良好对接,实现灵活部署。且部署简单,只有一个可执行文件,参数极少,一条命令即可启动一个Minio系统。Minio为了高性能采取无元数据数据库设计,避免元数据库成为整个系统的性能瓶颈,并将故障限制在单个集群之内,从而不会涉及其他集群。Minio同时完全兼容S3接口,因此也可以作为网关使用,对外提供S3访问。同时使用Minio Erasure code和checksum 来防止硬件故障。即使损失一半以上的硬盘,但是仍然可以从中恢复。分布式中也允许(N/2)-1个节点故障。
docker+rancher部署minio集群
在rancher上创建三个minio服务,这三个选择的服务器ip是三台不同的服务器ip,在主机调度中进行选择不同的主机
端口映射:
配置环境变量:
主机调度
每个指定不同的主机,三台机器分别运行一个minio
配置数据卷
在三台主机主机的host文件中添加host对应关系
ip1 minio-1
ip2 minio-2
ip3 minio-3
数据卷配置
网络
使用主机网络:是
入库命令
server –console-address :9090 http://minio-{1…3}/data{1…2}
安装ngxin负载均衡访问minio在rancher上创建nginx服务
数据卷映射
进入主机目录配置nginx,
vi /mnt/nginx/conf.d
内容如下:
worker_processes 1;
events {
worker_connections 1024;
}
http{
#上面安装主机的三台机器记得修改
upstream http_minio_back{
server ip1:9000;
server ip2:9000;
server ip3:9000;
}
server{
listen 6555;
server_name localhost;
ignore_invalid_headers off;
#配置成上传文件不限制大小
client_max_body_size 0;
proxy_buffering off;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
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_connect_timeout 300;
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_ignore_client_abort on;
proxy_pass http_minio_back
}
}
}
}
数据卷配置引入conf.d
springboot+minio 实例如下:
pom.xml
io.minio
minio
8.0.3
application.yml配置:
spring:
# 配置文件上传大小限制
servlet:
multipart:
max-file-size: 500MB
max-request-size: 500MB
oss:
endpoint: ${ENDPOINT:http://nginx的ip:nginx的端口号}
access-key: ${ACCESS_KEY:minio}
secret-key: ${SECRET_KEY:minio}
minio配置类
@Configuration
@ApiModel(value = "minio配置")
public class MinIoClientConfig {
@Value("${oss.endpoint}")
private String url;
@Value("${oss.access-key}")
private String accessKey;
@Value("${oss.secret-key}")
private String secretKey;
@Bean
public MinioClient minioClient(){
return MinioClient.builder()
.endpoint(url)
.credentials(accessKey, secretKey)
.build();
}
}
编写工具类用于将公用方法写到工具类中,实现调用
import cn.hutool.core.io.FastByteArrayOutputStream;
import com.alibaba.nacos.common.util.UuidUtils;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
@Component
@Slf4j
@RequiredArgsConstructor
public class MinioUtil {
//必须使用注入的方式否则会出现空指针
@Autowired
MinioClient minioClient;
/**
* 查看存储bucket是否存在
* bucketName 需要传入桶名
* @return boolean
*/
public Boolean bucketExists(String bucketName) {
Boolean found;
try {
found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return found;
}
/**
* 创建存储bucket
* bucketName 需要传入桶名
* @return Boolean
*/
public Boolean makeBucket(String bucketName) {
try {
minioClient.makeBucket(MakeBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 删除存储bucket
* bucketName 需要传入桶名
* @return Boolean
*/
public Boolean removeBucket(String bucketName) {
try {
minioClient.removeBucket(RemoveBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 获取全部bucket
*/
public List getAllBuckets() {
try {
List buckets = minioClient.listBuckets();
return buckets;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 文件上传
*
* @param file 文件
* BucketName 需要传入桶名
* @return Boolean
*/
public String upload(MultipartFile file) {
String originalFilename = file.getOriginalFilename();
if (StringUtils.isBlank(originalFilename)){
throw new RuntimeException();
}
String fileName = UuidUtils.generateUuid() + originalFilename.substring(originalFilename.lastIndexOf("."));
String objectName = "sssss" + "/" + fileName;
try {
PutObjectArgs objectArgs = PutObjectArgs.builder().bucket("BucketName").object(objectName)
.stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();
//文件名称相同会覆盖
minioClient.putObject(objectArgs);
} catch (Exception e) {
e.printStackTrace();
return null;
}
return objectName;
}
/**
* 预览
* @param fileName
* BucketName 需要传入桶名
* @return
*/
public String preview(String fileName){
// 查看文件地址
GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket("BucketName").object(fileName).method(Method.GET).build();
try {
String url = minioClient.getPresignedObjectUrl(build);
return url;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 文件下载
* @param fileName 文件名称
* BucketName 需要传入桶名
* @param res response
* @return Boolean
*/
public void download(String fileName, HttpServletResponse res) {
GetObjectArgs objectArgs = GetObjectArgs.builder().bucket("BucketName")
.object(fileName).build();
try (GetObjectResponse response = minioClient.getObject(objectArgs)){
byte[] buf = new byte[1024];
int len;
try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()){
while ((len=response.read(buf))!=-1){
os.write(buf,0,len);
}
os.flush();
byte[] bytes = os.toByteArray();
res.setCharacterEncoding("utf-8");
// 设置强制下载不打开
// res.setContentType("application/force-download");
res.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
try (ServletOutputStream stream = res.getOutputStream()){
stream.write(bytes);
stream.flush();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 查看文件对象
* BucketName 需要传入桶名
* @return 存储bucket内文件对象信息
*/
public List listObjects() {
Iterable> results = minioClient.listObjects(
ListObjectsArgs.builder().bucket("BucketName").build());
List items = new ArrayList();
try {
for (Result result : results) {
items.add(result.get());
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return items;
}
/**
* 删除
* @param fileName
* BucketName 需要传入桶名
* @return
* @throws Exception
*/
public boolean remove(String fileName){
try {
minioClient.removeObject( RemoveObjectArgs.builder().bucket("BucketName").object(fileName).build());
}catch (Exception e){
return false;
}
return true;
}
}
测试
@RequestMapping("/tesss")
@RestController
public class TestCon {
@Autowired
MinioUtil minioUtil;
@GetMapping("/test")
public Boolean bucketExists(String bucketName) {
return minioUtil.bucketExists(bucketName);
}
@GetMapping("/test123")
public Boolean makeBucket(String bucketName) {
return minioUtil.makeBucket(bucketName);
}
}
如果想要在上传完成后再进行下一步的操作 ,应该进行如下操作
@Async
public CompletableFuture uploadFile(String bucketName, String fileName, InputStream stream,PutObjectOptions putObjectOptions){
CompletableFuture future = new CompletableFuture();
try {
minioClient.putObject(bucketName, fileName, stream, putObjectOptions);
future.complete("上传成功");
} catch (MinioException | InvalidKeyException | IOException | NoSuchAlgorithmException
| IllegalArgumentException e) {
future.completeExceptionally(e);
}
return future;
}
调用上述方法实现判断文件是否上传成功
public String uploadFileCallAlgorithm(MultipartFile file, String algType,String arithmeticCode) throws IOException {
PutObjectOptions putObjectOptions = new PutObjectOptions(file.getSize(), PutObjectOptions.MIN_MULTIPART_SIZE);
CompletableFuture future = this.uploadFile(BucketName, fileName, file.getInputStream(), putObjectOptions);
future.thenAccept(results -> {
//此处可写上传文件完成后的实现逻辑
}).exceptionally(ex -> {
return null;
}).join();
return miningCode;
}else {
return "请上传csv文件";
}
}