MinIO
MinIO 是一个高性能的对象存储系统,专为私有云和混合云环境设计,支持存储海量的非结构化数据,如图片、视频、备份、日志文件等。MinIO 提供了与 Amazon S3 兼容的 API,因此它通常用于构建可自托管的对象存储解决方案,尤其在那些需要高可用性和扩展性的场景中。
MinIO的主要特性
与 S3 兼容的 API:MinIO 完全兼容 AWS S3 的 API。这意味着你可以使用现有的 S3 客户端库和工具来与 MinIO 交互,这使得它非常适合那些希望在本地或私有云环境中复用 S3 代码和工具的用户。
横向扩展:MinIO 允许通过增加更多节点来横向扩展存储容量和性能。这使得它能够随着业务需求的增长轻松扩展,从而满足海量数据存储的需求。
高性能:MinIO 被设计为一个极高性能的对象存储系统。它利用现代硬件的多核处理能力和高速网络,能够处理大规模的存储请求,特别适合数据密集型任务,如机器学习、数据分析和视频流等。
分布式存储:MinIO 支持分布式部署,能够通过多个节点进行数据复制和分片,从而保证数据的高可用性和持久性。即使某个节点失效,数据也能通过冗余机制进行恢复。
Kubernetes 支持:MinIO 天然与 Kubernetes 兼容,能够在容器化环境中进行自动化管理和扩展,适合云原生应用。
高可用性和容错性:通过分布式部署,MinIO 提供了容错和数据冗余功能。即使某些磁盘或节点故障,系统仍然能够继续工作并确保数据完整性。
加密和安全性:MinIO 支持服务器端加密和传输层加密(TLS),能够保护数据的机密性和完整性。此外,MinIO 支持基于策略的访问控制(IAM)和 LDAP 等集成,提供精细化的权限管理。
MinIO的架构
MinIO 采用微服务架构,特别注重简洁和高效。它没有复杂的依赖关系,所有功能都打包在一个二进制文件中。这使得 MinIO 在多种场景下的部署和管理非常简单。
单一二进制文件:MinIO 通过单一的二进制文件提供了所有功能,包括对象存储、API 接口、安全、容错等。这简化了安装、部署和维护的过程。
无状态架构:MinIO 是一个无状态服务,这意味着它没有依赖外部数据库或元数据存储。所有元数据和文件数据都存储在磁盘上或通过分布式文件系统管理。
水平扩展:MinIO 通过增加节点的方式进行水平扩展,且每个节点可以是物理机、虚拟机或容器。扩展时只需将新节点加入集群即可自动扩展存储能力。
多租户支持:MinIO 支持多租户架构,能够为多个应用或团队提供隔离的存储空间。
MinIO的典型使用场景
大数据分析:MinIO 可用于存储海量数据,为大数据平台(如 Apache Spark、Hadoop 等)提供高性能的存储后端。
机器学习:机器学习模型的训练通常需要大量的图像、视频或日志文件,MinIO 的高吞吐和可扩展性非常适合这类场景。
备份和恢复:MinIO 常被用作企业的备份解决方案,能够存储大量的备份数据,并确保其高可用性。
私有云和混合云:许多企业希望在私有云或混合云中构建与 S3 兼容的存储解决方案,以降低公共云存储的成本或满足数据合规性需求。
数据湖存储:MinIO 是现代数据湖架构的理想存储层,它可以无缝地与大数据工具集成,支持对非结构化和半结构化数据进行高效管理。
Kubernetes:MinIO 可以作为 Kubernetes 中的持久存储,支持动态存储卷和持久化卷声明(PVC)。
CI/CD:通过与 Jenkins、GitLab 等 CI/CD 工具的集成,MinIO 可以用于存储构建工件、备份文件等。
MinIO的安装与使用
MinIO 的安装非常简单,可以在各种操作系统(Linux、macOS、Windows)上运行。
首先,是单机安装方式:
1 2 3 4 5
| wget https://dl.min.io/server/minio/release/linux-amd64/minio chmod +x minio
./minio server /data
|
当然,还有比较简单的方法是docker安装:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| docker pull minio/minio
mkdir -p /home/minio/config mkdir -p /home/minio/data
docker run -p 9000:9000 -p 9090:9090 \ --net=host \ --name minio \ -d --restart=always \ -e "MINIO_ACCESS_KEY=gagaduck" \ -e "MINIO_SECRET_KEY=gagaduck" \ -v /home/minio/data:/data \ -v /home/minio/config:/root/.minio \ minio/minio server \ /data --console-address ":9090" -address ":9000"
|
运气启动后,进入localhost:9090:

输入启动设置的账号密码,进入minio前端UI界面:

这里介绍一下minio文件管理的一些简单的基本概念:
对象(Object):对象是 MinIO 存储系统中的核心单位。每个对象由文件数据和元数据组成,元数据包括对象的大小、创建时间、内容类型等。
存储桶(Bucket):MinIO 使用存储桶(类似于文件夹)来组织对象。每个存储桶可以包含任意数量的对象,且存储桶名必须是唯一的。
元数据:MinIO 允许用户为每个对象设置自定义元数据,帮助更好地组织和管理文件。
生命周期管理:通过配置 MinIO 的生命周期策略,可以自动管理对象的存储和删除,例如定期删除过期对象或将对象移动到低成本存储层。
版本控制:MinIO 支持对象版本控制,能够跟踪文件的不同版本。开启版本控制后,更新对象时不会覆盖旧版本,而是保留历史版本。
数据复制:MinIO 支持跨数据中心的数据复制,允许用户将文件从一个存储桶自动复制到另一个存储桶,实现高可用性和数据备份。
除了通过可视化界面操作,也可以通过MinIO控制台或者mc命令行工具,对minio进行操作,如:
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
| mc mb myminio/mybucket
mc cp /path/to/local/file myminio/mybucket/file
mc cp myminio/mybucket/file /path/to/local/file
mc rm myminio/mybucket/file
mc rb myminio/mybucket
mc ls myminio/mybucket
mc ls myminio
mc stat myminio/mybucket/file
mc policy set public myminio/mybucket
……
|
在单点minio的基础上,还可进一步搭建分布式minio集群,通过多个节点实现数据分片和冗杂,提高可用性和扩展性。
以下以Docker 搭建分布式 MinIO 集群为例:
首先,需要确保安装了docker和docker-compose,至少需要4个节点或者说容器来搭建这样一个分布式集群,每个节点储存一部分数据,来提高容错和性能。
下一步就是创建一个docker-compose.yml文件来定义MinIO服务。可参考如下:
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
| version: '3'
services: minio1: image: minio/minio volumes: - data1:/data ports: - "9001:9000" - "9096:9090" environment: MINIO_ROOT_USER: admin MINIO_ROOT_PASSWORD: admin123 command: server --address ":9000" --console-address ":9090" http: networks: - minio_distributed
minio2: image: minio/minio volumes: - data2:/data ports: - "9002:9000" - "9097:9090" environment: MINIO_ROOT_USER: admin MINIO_ROOT_PASSWORD: admin123 command: server --address ":9000" --console-address ":9090" http: networks: - minio_distributed
minio3: image: minio/minio volumes: - data3:/data ports: - "9003:9000" - "9098:9090" environment: MINIO_ROOT_USER: admin MINIO_ROOT_PASSWORD: admin123 command: server --address ":9000" --console-address ":9090" http: networks: - minio_distributed
minio4: image: minio/minio volumes: - data4:/data ports: - "9004:9000" - "9099:9090" environment: MINIO_ROOT_USER: admin MINIO_ROOT_PASSWORD: admin123 command: server --address ":9000" --console-address ":9090" http: networks: - minio_distributed
volumes: data1: data2: data3: data4:
networks: minio_distributed: driver: bridge
|
每个服务(minio1、minio2、minio3、minio4)代表一个 MinIO 实例,分别绑定不同的端口(9001 到 9004)。其控制台也是,从9096-9099。volumes用于挂载本地数据目录,将数据持久化到容器外部。command指定了分布式模式下各个节点的地址,所有节点互相可见,组成集群,同时也指定控制台IP和服务地址。环境变量 MINIO_ROOT_USER 和 MINIO_ROOT_PASSWORD 用于设置集群的访问凭证。
在此基础上,启动集群:
如图所示,为一个四个节点的minio集群:

每个 MinIO 节点都有自己的 Web 控制台,你可以通过访问 http://localhost:9096、http://localhost:9097、http://localhost:9098、http://localhost:9099 来查看各个节点的状态。
使用配置的管理员账号(admin 和 admin123)登录到控制台。

而后可以做一些验证,比如现在shutdown掉某个节点(9097),继续查看9096,发现没有问题的。在集群中上传文件时,数据会自动分布在不同节点上。如果某个节点发生故障,其他节点仍然可以提供数据存取服务,确保数据的高可用性。
此外,在分布式集群中,MinIO 会自动处理数据的分片和复制。通过配置数据冗余,你可以增加容错能力,确保即使多个节点故障,数据仍然安全。
还可以自行配置一些如桶的复制策略等等的内容:

SpringBoot集成MinIO
对于一些需要高效管理大规模非结构化数据时(如图像、视频、日志等)的项目来说,MinIO 提供了类似于 AWS S3 的对象存储功能,且完全开源、易于部署,因此非常适合与 Spring Boot 集成使用。
通过将minio和springboot进行集成,有利于实现简便的对象存储操作,一些常见的场景比如多媒体管理的场景、日志和备份管理的场景、文件共享平台等等。
对于minio和springboot的集成,可以参考https://github.com/gagaducko/springboot-minio-example
首先,是需要加入相关的依赖:
1 2 3 4 5
| <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.5.9</version> </dependency>
|
在此基础上,配置minio的一些信息,如:
1 2 3 4
| minio.endpoint=http://192.168.186.1:9000/ minio.accessKey=P4QLPVGGSQD3LX5OMG46 minio.secretKey=xdZSChR6PM0PlUWxzMlqo+oZzURP5gClHv2IFsb3 minio.bucketName=gagaduck
|
需要注意的是,这个accessKey和secretKey需要进入minio配置。详细位置参考下图,在service accounts中:

而后可以创建一个minio的配置类进行配置:
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
| @Data @Configuration @ConfigurationProperties(prefix = "minio") public class MinioClientConfig {
private String endpoint;
private String accessKey;
private String secretKey;
private String bucketName;
@Bean public MinioClient minioClient() { return MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); } }
|
对minio的操作参见如下component:
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 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
| @Component @Slf4j public class MinioClientUtil {
@Resource private MinioClient minioClient;
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; }
public Boolean makeBucket(String bucketName) { try { minioClient.makeBucket(MakeBucketArgs.builder() .bucket(bucketName) .build()); } catch (Exception e) { e.printStackTrace(); return false; } return true; }
public Boolean removeBucket(String bucketName) { try { minioClient.removeBucket(RemoveBucketArgs.builder() .bucket(bucketName) .build()); } catch (Exception e) { e.printStackTrace(); return false; } return true; }
public List<JSONObject> getAllBuckets() { try { List<Bucket> buckets = minioClient.listBuckets(); List<JSONObject> jsonObjects = new ArrayList<>(); for (Bucket bucket : buckets) { JSONObject jsonObject = new JSONObject(); jsonObject.put("name", bucket.name()); jsonObject.put("creationDate", bucket.creationDate()); jsonObjects.add(jsonObject); } return jsonObjects; } catch (Exception e) { e.printStackTrace(); } return null; }
public String upload(String bucketName, MultipartFile file) { String originalFilename = file.getOriginalFilename(); if (StringUtils.isBlank(originalFilename)){ throw new RuntimeException(); }
String fileName = originalFilename; DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM/dd"); LocalDateTime now = LocalDateTime.now(); String formattedDate = now.format(formatter); String objectName = formattedDate + "/" + 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; }
public String preview(String bucketName, String fileName){ GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket(bucketName).object(fileName).method(Method.GET).build(); try { return minioClient.getPresignedObjectUrl(build); } catch (Exception e) { e.printStackTrace(); } return null; }
public void download(String bucketName, String fileName, HttpServletResponse res) { GetObjectArgs objectArgs = GetObjectArgs.builder() .bucket(bucketName) .object(fileName) .build(); try (GetObjectResponse response = minioClient.getObject(objectArgs); ServletOutputStream outputStream = res.getOutputStream()) { res.setCharacterEncoding("utf-8"); res.setContentType("application/octet-stream"); String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20"); res.addHeader("Content-Disposition", "attachment;filename=\"" + encodedFileName + "\""); byte[] buf = new byte[8192]; int len; while ((len = response.read(buf)) != -1) { outputStream.write(buf, 0, len); } outputStream.flush(); } catch (MinioException | IOException | InvalidKeyException | NoSuchAlgorithmException e) { e.printStackTrace(); } }
public List<JSONObject> listObjects(String bucketName) { return listObjectsRecursive(bucketName, ""); }
private List<JSONObject> listObjectsRecursive(String bucketName, String prefix) { Iterable<Result<Item>> results = minioClient.listObjects( ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(true).build()); List<JSONObject> items = new ArrayList<>(); for (Result<Item> result : results) { try { Item item = result.get(); JSONObject jsonObject = new JSONObject(); jsonObject.put("isDir", item.isDir()); jsonObject.put("ObjectName", item.objectName()); jsonObject.put("size", item.size()); jsonObject.put("lastModified", item.lastModified()); items.add(jsonObject); } catch (Exception e) { e.printStackTrace(); } } return items; }
public boolean remove(String bucketName, String fileName){ try { minioClient.removeObject( RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build()); }catch (Exception e){ return false; } return true; }
}
|
再进一步实现对应的controller等等内容。
做一个简单的总结,Spring Boot 集成 MinIO 的好处在于它提供了一个轻量级、兼容 S3 的对象存储解决方案,支持高可用、分布式存储,以及精细的访问控制。特别适合需要处理大规模文件、日志和备份的应用场景,通过与 Spring 的无缝集成,可以显著提升文件管理的效率和安全性。