背景
项目中没实际用过minio,印象中记得应该是时序数据库之类的。入职新公司后在维护的项目中遇到了,才发现它是对象存储,和印象南辕北辙。本着学习和融会贯通的精神进行归纳总结。
minio什么
官网:适用于 AI 的高性能对象存储。
MinIO 是一种高性能、S3 兼容的对象存储。它专为大规模 AI/ML、数据湖和数据库工作负载而构建。它在本地和任何云(公共或私有)上运行,从数据中心到边缘。MinIO是 GNU AGPL v3 下的软件定义和开源软件。高性能
MinIO 是世界上最快的对象存储,在 32 个 NVMe 驱动器节点和 100Gbe 网络上发布的 GET/PUT 结果分别超过 325 GiB/秒和 165 GiB/秒。
通过原生 Kubernetes 操作集成,MinIO 支持公共云、私有云和边缘云上的所有主要 Kubernetes 发行版。
个人理解:
怎么适用于AI不清楚,不就是对象存储服务吗?和其他云产品的oss(阿里云、腾讯云、七牛云等)应该大差不差,和k8s集成算是亮点吧。
使用场景
分布式存储:在多台服务器上构建分布式存储集群,实现高可用性和可伸缩性。对于需要大规模存储数据的应用非常有用,例如大数据分析、日志存储等。
对象存储:MinIO适用于存储各种类型的对象,如图像、视频、文档等。它提供了用于管理和组织这些对象的功能。 对于需要存储和传送大量媒体文件(如音频、视频)的应用程序,MinIO的高性能和可伸缩性使其成为一个很好的选择。
备份和归档:MinIO可以作为数据备份和长期归档的解决方案。您可以将不经常访问但需要保留的数据存储在MinIO集群中,以便随时检索。
云原生应用程序:MinIO与云原生生态系统(如Kubernetes)集成紧密,它适用于构建容器化应用程序,特别是需要对象存储的场景。
数据库:MinIO可以用作构建数据湖(Data Lake)架构的一部分,存储各种类型和格式的数据,以便进一步分析和处理。
IoT数据存储:对于需要存储大量传感器数据和IoT设备生成的数据的场景,MinIO提供了一个可靠的存储解决方案。
数据共享和写作:MinIO允许您轻松共享存储的对象,为多个用户或应用程序提供数据访问权限。
对比云oss和服务器文件存储
minio vs 云OSS产品(此处以阿里云OSS示例)
1、开源和托管服务:minio可以在自己的服务器上部署和管理,阿里云对象存储是基于云托管提供的服务;
2、部署和维护:minio自行部署后期需要投入管理和运维人本,阿里云则不需要,遇到问题提工单即可;
3、功能和生态:阿里云有一套完整的生态,包含与其他阿里云服务的集成,可具备高可用,付费即可,而minio则需要自己编制和部署合适的集群方案满足高可用;
4、安全性和权限控制:阿里云OSS提供了多层次的数据安全性和权限控制,包括数据加密、访问控制、防盗链等功能,而minio需要根据需求进行配置;
5、定价模式:minio开源版本免费,阿里云oss收费;
6、定制化:阿里云oss可以理解为产品,产品的定制化差一些。
minio vs 服务器文件存储
1、数据模型:传统服务器文件存储采用文件系统来管理文件和目录。对象存储使用一种更灵活的数据模型,其中每个文件被视为一个对象,拥有唯一的标识符(对象键),并可以包含自定义的元数据。
2、可伸缩性:MinIO和对象存储是分布式的,可以水平扩展以适应大规模数据存储需求。传统服务器文件存储可能需要手动配置和管理集群以实现扩展性。
3、高可用性:对象存储通常设计为具有高可用性。数据在多个地理位置进行复制,以确保数据的冗余备份。传统服务器存储可能需要额外的冗余设备和配置来实现高可用性。
4、数据访问:对象存储提供HTTP或类似的API来访问数据,使其适用于云原生应用程序和跨网络的访问。传统服务器存储可能需要通过网络文件共享协议(如SMB或NFS)来访问数据。
5、备份和恢复:对象存储通常具有内置的备份和版本控制机制,使数据的备份和恢复更加容易。传统服务器存储可能需要单独的备份解决方案。
6、元数据和搜索:对象存储可以存储自定义元数据,并具有用于搜索和组织数据的功能。传统服务器存储可能不太适合存储大量数据和元数据。
安装
无其他依赖,安装较简单,此文以windows安装示例。
下载服务端安装文件,官网下载地址:https://dl.min.io/server/minio/release/windows-amd64/minio.exe
cd到minio.exe目录,执行
minio.exe server E:\data\minio --console-address ":9001"
启动成功如下图:
使用
客户端:
浏览器访问:http://127.0.0.1:9001/browser/test-minio,用户名密码见服务端启动日志,minioadmin/minioadmin,可通过图形界面管理文件,如下图:
API:
以在springboot项目中使用示例
1、引入minio.jar
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.0.3</version>
</dependency>
2、配置信息
minio连接初始化:
3、封装minio操作类,示例代码如下:
package com.knowology.common.util;
import io.minio.*;
import io.minio.errors.ErrorResponseException;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@RequiredArgsConstructor
@Component
public class MinioUtil {
private final MinioClient minioClient;
public static String bucketName = "bucketNameTest";
public void clean() {
Iterable<Result<Item>> results = this.listObjects(bucketName);
ZonedDateTime before7Days = ZonedDateTime.now().minusDays(7);
results.forEach(result -> {
try {
Item item = result.get();
ZonedDateTime zonedDateTime = item.lastModified();
if (zonedDateTime.isBefore(before7Days)) {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object((item.objectName())).build());
}
} catch (Exception e) {
log.error("clean minio", e);
}
});
}
/**
* 检查存储桶是否存在
*
* @param bucketName 存储桶名称
* @return
*/
@SneakyThrows
public boolean bucketExists(String bucketName) {
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
}
/**
* 创建存储桶
*
* @param bucketName 存储桶名称
*/
@SneakyThrows
public void makeBucket(String bucketName) {
if (!bucketExists(bucketName)) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
}
/**
* 列出所有存储桶
*
* @return
*/
@SneakyThrows
public List<Bucket> listBuckets() {
return minioClient.listBuckets();
}
@SneakyThrows
public GetObjectResponse getObject(GetObjectArgs args) {
return minioClient.getObject(args);
}
/**
* 列出所有存储桶名称
*
* @return
*/
public List<String> listBucketNames() {
return listBuckets().stream().map(Bucket::name).collect(Collectors.toList());
}
/**
* 列出存储桶中的所有对象
*
* @param bucketName 存储桶名称
* @return
*/
@SneakyThrows
public Iterable<Result<Item>> listObjects(String bucketName) {
return !bucketExists(bucketName) ? new ArrayList<>(0) : minioClient.listObjects(
ListObjectsArgs.builder()
.bucket(bucketName)
.build());
}
/**
* 列出存储桶中的所有对象名称
*
* @param bucketName 存储桶名称
* @return
*/
@SneakyThrows
public List<String> listObjectNames(String bucketName) {
List<String> listObjectNames = new ArrayList<>();
for (Result<Item> result : listObjects(bucketName)) {
listObjectNames.add(result.get().objectName());
}
return listObjectNames;
}
/**
* 上传MultipartFile象
*
* @param bucketName
* @param filename
* @param multipartFile
*/
@SneakyThrows
public void putObject(String bucketName, String filename, MultipartFile multipartFile) {
makeBucket(bucketName);
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(bucketName)
.object(filename)
.stream(multipartFile.getInputStream(), multipartFile.getSize(), -1)
.contentType(multipartFile.getContentType())
.build();
minioClient.putObject(putObjectArgs);
}
/**
* 通过InputStream上传对象
*
* @param bucketName 存储桶名称
* @param objectName 存储桶里的对象名称
* @param stream 要上传的流
*/
@SneakyThrows
public void putObject(String bucketName, String objectName, InputStream stream) {
makeBucket(bucketName);
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(stream, -1, 10485760)
.build();
minioClient.putObject(putObjectArgs);
}
/**
* 从指定位置复制一份文件到指定位置
*
* @param bucket
* @param object
* @param sourceBucket
* @param sourceObject
*/
@SneakyThrows
public void copyObject(String bucket, String object, String sourceBucket, String sourceObject) {
CopyObjectArgs copyObjectArgs = CopyObjectArgs.builder()
.bucket(bucket)
.object(object)
.source(CopySource.builder()
.bucket(sourceBucket)
.object(sourceObject)
.build())
.build();
minioClient.copyObject(copyObjectArgs);
}
/**
* 删除存储桶
*
* @param bucketName 存储桶名称
* @return
*/
@SneakyThrows
public boolean removeBucket(String bucketName) {
if (bucketExists(bucketName)) {
return false;
}
Iterable<Result<Item>> myObjects = listObjects(bucketName);
for (Result<Item> result : myObjects) {
// 有对象文件,则删除失败
if (result.get().size() > 0) {
return false;
}
}
// 删除存储桶,注意,只有存储桶为空时才能删除成功。
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
return !bucketExists(bucketName);
}
/**
* 删除一个对象
*
* @param bucketName 存储桶名称
* @param objectName 存储桶里的对象名称
*/
@SneakyThrows
public boolean removeObject(String bucketName, String objectName) {
if (bucketExists(bucketName)) {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
return true;
}
return false;
}
/**
* 删除指定桶的多个文件对象,返回删除错误的对象列表,全部删除成功,返回空列表
*
* @param bucketName 存储桶名称
* @param objectNames 含有要删除的多个object名称的迭代器对象
* @return
*/
@SneakyThrows
public List<String> removeObject(String bucketName, List<String> objectNames) {
List<String> deleteErrorNames = new ArrayList<>();
if (bucketExists(bucketName)) {
Iterable<DeleteObject> objectIterator = objectNames.stream()
.map(DeleteObject::new)
.collect(Collectors.toList());
Iterable<Result<DeleteError>> results = minioClient.removeObjects(
RemoveObjectsArgs.builder()
.bucket(bucketName)
.objects(objectIterator)
.build());
for (Result<DeleteError> result : results) {
deleteErrorNames.add(result.get().objectName());
}
}
return deleteErrorNames;
}
/**
* 以流的形式获取一个文件对象
*
* @param bucketName 存储桶名称
* @param objectName 存储桶里的对象名称
* @return
*/
@SneakyThrows
public InputStream getObject(String bucketName, String objectName) {
return !bucketExists(bucketName) ? null : minioClient.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
}
/**
* 以流的形式获取一个文件对象(断点下载)
*
* @param bucketName 存储桶名称
* @param objectName 存储桶里的对象名称
* @param offset 起始字节的位置
* @param length 要读取的长度 (可选,如果无值则代表读到文件结尾)
* @return
*/
@SneakyThrows
public InputStream getObject(String bucketName, String objectName, long offset, Long length) {
return !bucketExists(bucketName) ? null : minioClient.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.offset(offset)
.length(length)
.build());
}
/**
* 下载并将文件保存到本地
*
* @param bucketName 存储桶名称
* @param objectName 存储桶里的对象名称
* @param fileName File name
* @return
*/
@SneakyThrows
public boolean getObject(String bucketName, String objectName, String fileName) {
if (bucketExists(bucketName)) {
minioClient.downloadObject(
DownloadObjectArgs.builder()
.bucket(objectName)
.object(fileName)
.filename(fileName)
.build());
return true;
}
return false;
}
/**
* 文件访问路径。
* 但是需要验证
*
* @param bucketName 存储桶名称
* @param objectName 存储桶里的对象名称
* @return
*/
public String getUrl(String bucketName, String objectName) {
String url = getTemporaryUrl(bucketName, objectName);
return url.lastIndexOf("?") > 0 ? url.substring(0, url.lastIndexOf("?")) : url;
}
/**
* 生成一个给HTTP GET请求用的文件访问URL。
*
* @param bucketName
* @param objectName
* @return
*/
public String getTemporaryUrl(String bucketName, String objectName) {
return getTemporaryUrl(bucketName, objectName, 7*24*60*60*1000);
}
/**
* 生成一个给HTTP GET请求用的文件访问URL。
* 浏览器/移动端的客户端可以用这个URL进行下载,即使其所在的存储桶是私有的。这个文件访问URL可以设置一个失效时间,默认值是7天。
*
* @param bucketName 存储桶名称
* @param objectName 存储桶里的对象名称
* @param expires 失效时间(以秒为单位),默认是7天,不得大于七天
* @return
*/
@SneakyThrows
public String getTemporaryUrl(String bucketName, String objectName, Integer expires) {
return !bucketExists(bucketName) ? "" : minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(objectName)
.expiry(expires)
.build());
}
public boolean objectExists(String bucketName, String objectName) {
try {
minioClient.statObject(StatObjectArgs.builder()
.bucket(bucketName)
.object(objectName).build());
return true;
} catch (ErrorResponseException e) {
return false;
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
总结
minio基于较低的使用门槛可作为服务器文件存储和云OSS产品的替代方案,可作为以后项目文件存储相关的替换方案。目前仅在使用层面浅接触,待后续使用中体会。印象中minio是时序数据库,应该是记错了。