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 minio8.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 ListgetAllBuckets() { 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 CompletableFutureuploadFile(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); CompletableFuturefuture = this.uploadFile(BucketName, fileName, file.getInputStream(), putObjectOptions); future.thenAccept(results -> { //此处可写上传文件完成后的实现逻辑 }).exceptionally(ex -> { return null; }).join(); return miningCode; }else { return "请上传csv文件"; } }