探索MinIO:高性能、分布式对象存储解决方案

注:本文除代码外多数为AI生成

最近因为有项目需要换成Amazon S3的云存储,所以把之前做过的minio部分做一个记录,后面也会把基于这版改造的S3方法发出来记录。

MinIO简介

MinIO是一款高性能、分布式对象存储服务器,设计用于在大规模环境中存储和检索非结构化数据集,如图像、视频和日志文件。它完全开源,遵循Apache License v2.0,并且与Amazon S3 API兼容,这使得从现有的S3环境迁移变得简单。MinIO支持多租户,确保了数据的安全性和隔离性,同时提供了多种数据持久性和一致性保障机制。由于其出色的性能特性,如高吞吐量和低延迟,MinIO成为了众多企业和组织在构建现代云基础设施时的首选对象存储解决方案。

MinIO架构与技术

去中心化架构
  • 无共享架构:MinIO采用了一种去中心化的无共享架构,这意味着没有单一的瓶颈点或中心化的元数据服务器。数据和元数据分布在集群内的所有节点上,提高了系统的整体性能和可靠性。
  • 统一命名空间:尽管数据分布在多个节点上,但MinIO对外提供了一个统一的命名空间,用户无需关心数据的具体位置。
分布式特性
  • 水平扩展:MinIO可以很容易地通过添加更多的节点来水平扩展,每个节点都是对等的,可以独立运行和存储数据。
  • 纠删编码:为了提高数据的可靠性和容错性,MinIO使用纠删编码技术(Erasure Coding)来存储数据。这允许数据在多个节点上以冗余的形式存在,即使部分节点发生故障,数据仍然可读。
高性能
  • 并发处理:MinIO的设计考虑到了高并发性,能够同时处理大量的读写请求,提供低延迟和高吞吐量。
  • 网络优化:MinIO通过高效的网络协议和数据传输优化,确保数据在网络中的快速流动。
兼容性
  • S3 API兼容:MinIO完全兼容Amazon S3 API,这使得它可以无缝地与许多已有的应用程序和服务集成,降低了迁移成本。
安全性
  • 加密:MinIO支持静态数据加密,确保数据在存储期间的安全。
  • 访问控制:通过IAM(Identity and Access Management)策略,MinIO提供了细粒度的访问控制,保护数据不被未授权访问。
开源与跨平台
  • 开源许可证:MinIO遵循Apache License v2.0开源协议,允许自由使用、修改和分发。
  • 跨平台:MinIO可以在多种操作系统上运行,包括Linux、Windows和macOS,增强了其部署灵活性。
集群部署
  • 多节点集群:MinIO可以部署为一个集群,其中包含多个节点,这些节点共同维护数据的一致性和可用性。
  • 负载均衡:在集群中,可以通过DNS轮询或负载均衡器来分配请求到不同的节点,确保负载均匀分布。
技术栈
  • Go语言:MinIO使用Go语言编写,这提供了良好的性能和简洁的代码基础。

Minio的安装部署

单节点部署

  • 参考https://www.cnblogs.com/ComfortableM/p/17384523.html

集群部署概念

  1. 节点:MinIO集群由多个节点组成,每个节点都是一个独立运行的MinIO服务器实例。
  2. 纠删编码:为了实现数据冗余和容错,MinIO使用纠删编码(Erasure Coding)。这种技术允许数据被分割成多个片段,并且每个片段都有额外的校验信息。如果集群中有节点失效,数据仍然可以从剩余的节点中重构出来。
  3. 数据分布:数据均匀分布在所有节点上,以达到负载均衡和最大化使用所有存储资源的目的。

部署步骤

  1. 环境准备

    • 确保有足够的物理服务器或虚拟机。
    • 准备好足够的磁盘空间,每台服务器可以使用单个磁盘或多个磁盘。
    • 配置网络,确保所有节点之间可以互相通信。
  2. 软件安装
    • 在每台服务器上安装MinIO软件。可以通过二进制包、Docker容器或软件包管理系统完成。
    • 配置MinIO服务器,指定数据存储目录和集群相关的配置。
  3. 启动MinIO服务
    • 在每个节点上启动MinIO服务,使用集群模式的启动参数。
    • 指定集群中的其他节点,以便它们可以相互发现并形成集群。
  4. 验证集群状态
    • 使用MinIO的管理命令或Web UI检查集群状态,确认所有节点是否已成功加入集群。
    • 测试读写操作,确保数据可以正确地在集群中分布和访问。
  5. 监控和维护
    • 设置监控,持续监控集群的健康状况和性能指标。
    • 定期进行维护,如数据平衡、硬件升级或替换故障节点。

关键考虑因素

  • 节点数量:集群至少需要4个节点来实现数据的冗余和分布。通常,节点数量越多,数据的可用性和性能就越好。
  • 数据冗余:根据纠删编码策略,集群可以容忍一定数量的节点故障而不会丢失数据。
  • 网络配置:确保网络稳定和高速,因为集群中的节点需要频繁地相互通信。
  • 故障恢复:设计故障恢复计划,包括节点替换、数据重建和灾难恢复策略。
  • 性能调优:根据实际工作负载,可能需要调整网络带宽、磁盘I/O或其他系统参数来优化性能。

扩展和缩放

  • 扩展:通过添加更多节点可以轻松扩展MinIO集群的容量和性能。
  • 缩放:移除节点时需要小心,确保数据冗余不会降低到不可接受的水平。

注意事项

  • 使用hosts文件或DNS服务来解决集群内部的域名或IP地址,避免直接在配置文件中硬编码IP地址,这有助于在节点变化时更容易地维护集群。
  • 在部署多节点多磁盘的集群时,确保遵循最佳实践,如避免在同一服务器上使用过多的磁盘,以防服务器故障导致大量数据不可用。

下面为例:

  • 节点启动方式,每个节点都需要启动
export MINIO_ACCESS_KEY=admin
export MINIO_SECRET_KEY=minioadmin
#console-address参数可设可不设
./minio server --address ":9001" --console-address ":9011"
"http://ipA:9001/arcfile/minioData/data"
"http://ipB:9001/arcfile/minioData/data"
"http://ipC:9001/arcfile/minioData/data"
"http://ipD:9001/arcfile/minioData/data" >/arcfile/minioData/logs/start.txt 2>&1 &
  • 如果要用来测试,可以一个服务器部署四个节点。以上面为例修改端口启动,或者参考下面的启动脚本
RUNNING_USER=root
MINIO_HOME=/opt/minio
MINIO_HOST=192.168.222.10
#accesskey and secretkey
ACCESS_KEY=admin
SECRET_KEY=minioadmin for i in {01..04}; do
START_CMD="MINIO_ACCESS_KEY=${ACCESS_KEY} MINIO_SECRET_KEY=${SECRET_KEY} nohup ${MINIO_HOME}/minio server --address "${MINIO_HOST}:90${i}" http://${MINIO_HOST}:9001/opt/min-data1 http://${MINIO_HOST}:9002/opt/min-data2 http://${MINIO_HOST}:9003/opt/min-data3 http://${MINIO_HOST}:9004/opt/min-data4 > ${MINIO_HOME}/minio-90${i}.log 2>&1 &"
su - ${RUNNING_USER} -c "${START_CMD}"
done

集成Spring boot项目

添加依赖
        <dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.3.3</version>
</dependency> <!--如果出现okhttp依赖冲突,添加下面依赖-->
<!-- <dependency>-->
<!-- <groupId>com.squareup.okhttp3</groupId>-->
<!-- <artifactId>okhttp</artifactId>-->
<!-- <version>4.9.0</version>-->
<!-- </dependency>-->
创建配置类
import io.minio.MinioClient;
import io.minio.errors.*;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Logger; @Configuration
public class MinioClientConfig { private final Logger log = Logger.getLogger(this.getClass().getName()); @Value("${config.minio.url}")
private String MINIO_URL;
@Value("${config.minio.accessKey}")
private String MINIO_ACCESS_KEY;
@Value("${config.minio.secretKey}")
private String MINIO_SECRET_KEY; @Bean
public MinioClient getMinioClient() {
if (MINIO_URL.length() == 0 || MINIO_URL == null) {
throw new IllegalArgumentException("\n请正确配置Minio服务器的 URL 连接参数");
}
if (MINIO_ACCESS_KEY.length() == 0 || MINIO_ACCESS_KEY == null) {
throw new IllegalArgumentException("\n请正确配置Minio服务器的 ACCESS_KEY 连接参数");
}
if (MINIO_SECRET_KEY.length() == 0 || MINIO_SECRET_KEY == null) {
throw new IllegalArgumentException("\n请正确配置Minio服务器的 SECRET_KEY 连接参数");
}
MinioClient minioClient = MinioClient.builder()
.endpoint(MINIO_URL)
.credentials(MINIO_ACCESS_KEY, MINIO_SECRET_KEY)
.build();
try {
minioClient.listBuckets();
} catch (ErrorResponseException e) {
e.printStackTrace();
throw new RuntimeException("\nMinio服务器连接异常\n请检查所配置的Minio连接信息Access-key和Secret-Key是否正确");
} catch (InsufficientDataException e) {
e.printStackTrace();
} catch (InternalException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidResponseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("\nMinio服务器连接异常\n请检查Minio服务器是否已开启或所配置的Minio_url连接信息是否正确");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (ServerException e) {
e.printStackTrace();
} catch (XmlParserException e) {
e.printStackTrace();
}
log.info("Minio服务器连接成功,URL = " + MINIO_URL);
return minioClient;
}
}

基本操作介绍

  • 创建桶
public boolean createBucket(String bucketName) throws RuntimeException {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
  • 配置桶权限为公开
public void BucketAccessPublic(String bucketName) {
String config = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:ListBucketMultipartUploads\",\"s3:GetBucketLocation\",\"s3:ListBucket\"],\"Resource\":[\"arn:aws:s3:::" + bucketName + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::" + bucketName + "/*\"]}]}";
minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(config).build();
}
  • 上传对象

uploadObject()

//上传本地文件
public boolean uploadObject(String bucketName, String targetObject, String sourcePath) {
try {
minioClient.uploadObject(UploadObjectArgs.builder()
.bucket(bucketName)
.object(targetObject)
.filename(sourcePath)
.build());
} catch (ErrorResponseException e) {
e.printStackTrace();
return false;
} catch (InsufficientDataException e) {
e.printStackTrace();
} catch (InternalException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidResponseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (ServerException e) {
e.printStackTrace();
} catch (XmlParserException e) {
e.printStackTrace();
}
return true;
}

putObject()

public boolean putObject(String bucketName, String object, InputStream inputStream) {
try {
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(object)
//.contentType("application/pdf")不设置的话默认是"application/stream",这就是为什么某些文件上传上去无法直接预览的问题
.stream(inputStream, -1, 10485760)
//.tags(tags)上传可以直接携带标签
.build());
} catch (ErrorResponseException e) {
e.printStackTrace();
return false;
} catch (InsufficientDataException e) {
e.printStackTrace();
} catch (InternalException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidResponseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (ServerException e) {
e.printStackTrace();
} catch (XmlParserException e) {
e.printStackTrace();
}
return true;
}
  • 下载对象
    public GetObjectResponse getObject(String bucketName, String object) {
GetObjectResponse object1 = null;//这个对象是集成了InputStream的FilterInputStream类,所以是可以直接用流来处理文件的
try {
object1 = minioClient.getObject(GetObjectArgs.builder()
.bucket(bucketName)
.object(object)
.build());
} catch (ErrorResponseException e) {
e.printStackTrace();
} catch (InsufficientDataException e) {
e.printStackTrace();
} catch (InternalException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidResponseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (ServerException e) {
e.printStackTrace();
} catch (XmlParserException e) {
e.printStackTrace();
}
return object1;
}
  • 获取对象预签名Url
/**
* 生成预签名URL,允许外部在限定时间内访问指定的MinIO对象。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @param expire 过期时间,单位是分钟
* @param map 用于自定义HTTP响应头的映射,例如设置Content-Type
* @return 返回预签名的URL,可以用于直接访问对象
*/
public String presignedURLofObject(String bucketName, String object, int expire, Map<String, String> map) {
// 设置响应的内容类型为application/json
map.put("response-content-type", "application/json"); // 初始化预签名URL字符串
String presignedObjectUrl = ""; // 尝试获取预签名的URL
try {
// 构建获取预签名URL的参数
presignedObjectUrl = minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.bucket(bucketName) // 设置存储桶名称
.object(object) // 设置对象名称
.method(Method.GET) // 设置HTTP方法为GET
.expiry(expire, TimeUnit.MINUTES) // 设置过期时间为expire分钟后
//.extraQueryParams(map) // 注释掉的代码,用于设置额外的查询参数
.build() // 构建参数对象
);
} catch (ErrorResponseException e) {
// 处理错误响应异常
e.printStackTrace();
} catch (InsufficientDataException e) {
// 数据不足异常
e.printStackTrace();
} catch (InternalException e) {
// MinIO内部异常
e.printStackTrace();
} catch (InvalidKeyException e) {
// 无效的访问密钥异常
e.printStackTrace();
} catch (InvalidResponseException e) {
// 无效的响应异常
e.printStackTrace();
} catch (IOException e) {
// 输入输出异常
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// 不存在算法异常
e.printStackTrace();
} catch (XmlParserException e) {
// XML解析异常
e.printStackTrace();
} catch (ServerException e) {
// 服务器异常
e.printStackTrace();
}
// 返回预签名的URL
return presignedObjectUrl;
}

部分方法集

MinioService

import io.minio.ComposeSource;
import io.minio.GetObjectResponse;
import io.minio.Result;
import io.minio.StatObjectResponse;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import org.springframework.stereotype.Service; import java.io.InputStream;
import java.util.List;
import java.util.Map; /**
* MinioService接口,定义了与MinIO对象存储系统交互的方法集。
*/
@Service
public interface MinioService { /**
* 检查指定的存储桶是否存在。
*
* @param bucketName 存储桶名称
* @return 如果存储桶存在则返回true,否则返回false。
*/
boolean ifExistsBucket(String bucketName); /**
* 创建一个新的存储桶。
*
* @param bucketName 要创建的存储桶名称
* @throws RuntimeException 如果创建失败抛出运行时异常
* @return 如果创建成功则返回true,否则返回false。
*/
boolean createBucket(String bucketName) throws RuntimeException; /**
* 删除指定的存储桶。
*
* @param bucketName 要删除的存储桶名称
* @return 如果删除成功则返回true,否则返回false。
*/
boolean removeBucket(String bucketName); /**
* 列出所有已存在的存储桶。
*
* @return 包含所有存储桶信息的列表。
*/
List<Bucket> alreadyExistBuckets(); /**
* 列出指定存储桶中的对象。
*
* @param bucketName 要列出对象的存储桶名称
* @param predir 可选前缀,用于过滤对象名
* @param recursive 是否递归列出子目录的对象
* @return 包含匹配条件的对象列表。
*/
List<Result<Item>> listObjects(String bucketName, String predir, boolean recursive); /**
* 列出指定存储桶中的对象,用于组成对象操作。
*
* @param bucketName 要列出对象的存储桶名称
* @param predir 可选前缀,用于过滤对象名
* @return 包含对象信息的List<ComposeSource>对象列表。
*/
List<ComposeSource> listObjects(String bucketName, String predir); /**
* 复制一个对象到另一个位置。
*
* @param pastBucket 原始存储桶名称
* @param pastObject 原始对象名称
* @param newBucket 目标存储桶名称
* @param newObject 目标对象名称
* @return 如果复制成功则返回true,否则返回false。
*/
boolean copyObject(String pastBucket, String pastObject, String newBucket, String newObject); /**
* 下载存储桶中的对象到本地文件系统。
*
* @param bucketName 存储桶名称
* @param objectName 对象名称
* @param targetPath 目标文件路径
* @return 如果下载成功则返回true,否则返回false。
*/
boolean downObject(String bucketName, String objectName, String targetPath); /**
* 生成一个预签名的URL,允许外部访问指定的对象。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @param expire URL的有效期(分钟)
* @return 预签名的URL。
*/
String presignedURLofObject(String bucketName, String object, int expire); /**
* 生成一个预签名的URL,允许外部访问指定的对象,并自定义HTTP头部。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @param expire URL的有效期(分钟)
* @param map 自定义的HTTP头部信息
* @return 预签名的URL。
*/
String presignedURLofObject(String bucketName, String object, int expire, Map<String, String> map); /**
* 删除存储桶中的对象。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @return 如果删除成功则返回true,否则返回false。
*/
boolean deleteObject(String bucketName, String object); /**
* 上传本地文件到存储桶。
*
* @param bucketName 存储桶名称
* @param targetObject 目标对象名称
* @param sourcePath 本地文件路径
* @return 如果上传成功则返回true,否则返回false。
*/
boolean uploadObject(String bucketName, String targetObject, String sourcePath); /**
* 从InputStream上传数据到存储桶。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @param inputStream 数据流
* @return 如果上传成功则返回true,否则返回false。
*/
boolean putObject(String bucketName, String object, InputStream inputStream); /**
* 从InputStream上传数据到存储桶,并附带标签。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @param inputStream 数据流
* @param tags 对象的标签集合
* @return 如果上传成功则返回true,否则返回false。
*/
boolean putObject(String bucketName, String object, InputStream inputStream, Map<String, String> tags); /**
* 获取存储桶中的对象信息。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @return 对象的响应信息。
*/
GetObjectResponse getObject(String bucketName, String object); /**
* 检查存储桶中是否包含指定文件。
*
* @param bucketName 存储桶名称
* @param filename 文件名称
* @param recursive 是否递归查找
* @return 如果文件存在则返回true,否则返回false。
*/
boolean fileifexist(String bucketName, String filename, boolean recursive); /**
* 获取对象的元数据标签。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @return 对象的元数据标签集合。
*/
Map<String, String> getTags(String bucketName, String object); /**
* 添加或更新对象的标签。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @param addTags 要添加或更新的标签集合
* @return 如果操作成功则返回true,否则返回false。
*/
boolean addTags(String bucketName, String object, Map<String, String> addTags); /**
* 获取对象的状态信息。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @return 对象的状态信息。
*/
StatObjectResponse statObject(String bucketName, String object); /**
* 检查存储桶中对象是否存在。
*
* @param bucketName 存储桶名称
* @param objectName 对象名称
* @return 如果对象存在则返回true,否则返回false。
*/
boolean ifExistObject(String bucketName, String objectName); /**
* 从其他对象名中提取元名称。
*
* @param objectName 对象名称
* @return 提取的元名称。
*/
String getMetaNameFromOther(String objectName); /**
* 更改对象的标签。
*
* @param object 对象名称
* @param tag 新的标签值
* @return 如果更改成功则返回true,否则返回false。
*/
boolean changeTag(String object, String tag); /**
* 设置存储桶的公共访问权限。
*
* @param bucketName 存储桶名称
*/
void BucketAccessPublic(String bucketName);
}

MinioServiceImpl

import com.aspose.cad.internal.Y.S;
import com.xagxsj.erms.model.BucketName;
import com.xagxsj.erms.model.ObjectTags;
import com.xagxsj.erms.service.MinioService;
import com.xagxsj.erms.utils.FileUtil;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import io.minio.messages.Tags;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service; import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger; import static com.xagxsj.erms.model.BucketName.METADATA; @Service
public class MinioServiceImpl implements MinioService {
private final Logger log = Logger.getLogger(this.getClass().getName());
@Qualifier("getMinioClient")
@Autowired
MinioClient minioClient; /**
* 检查指定的存储桶是否存在于MinIO服务器上。
*
* @param bucketName 要检查的存储桶名称。
* @return 如果存储桶存在,则返回true;否则返回false。
*/
@Override
public boolean ifExistsBucket(String bucketName) {
boolean result = false;
try {
result = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (ErrorResponseException e) {
e.printStackTrace();
return false;
} catch (InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
}
return result;
} /**
* 在MinIO服务器上创建一个新的存储桶。
*
* @param bucketName 要创建的存储桶名称。
* @throws RuntimeException 如果尝试创建一个已存在的存储桶,则抛出此异常。
* @return 如果存储桶创建成功,则返回true;否则返回false。
*/
@Override
public boolean createBucket(String bucketName) throws RuntimeException {
if (ifExistsBucket(bucketName)) {
throw new RuntimeException("桶已存在");
}
try {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
}
return ifExistsBucket(bucketName);
} /**
* 从MinIO服务器上删除一个存储桶。
*
* @param bucketName 要删除的存储桶名称。
* @return 如果存储桶成功删除,则返回true;如果存储桶不存在,则返回true;否则返回false。
*/
@Override
public boolean removeBucket(String bucketName) {
if (!ifExistsBucket(bucketName)) {
return true;
}
try {
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
}
return !ifExistsBucket(bucketName);
} /**
* 列出MinIO服务器上所有存在的存储桶。
*
* @return 返回一个包含所有存储桶信息的列表。
*/
@Override
public List<Bucket> alreadyExistBuckets() {
List<Bucket> buckets = null;
try {
buckets = minioClient.listBuckets();
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
}
return buckets;
} /**
* 检查指定的文件是否存在于存储桶中。
*
* @param bucketName 存储桶名称。
* @param filename 要检查的文件名。
* @param recursive 是否递归搜索子目录。
* @return 如果文件存在,则返回true;否则返回false。
*/
@Override
public boolean fileifexist(String bucketName, String filename, boolean recursive) {
boolean flag = false;
Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
.bucket(bucketName)
.prefix(filename)
.recursive(recursive)
.maxKeys(1000)
.build());
for (Result<Item> result : results) {
try {
Item item = result.get();
if (item.objectName().equals(filename)) {
flag = true;
break;
}
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
}
}
return flag;
} /**
* 列出存储桶中所有对象,可指定前缀和是否递归搜索子目录。
*
* @param bucketName 存储桶名称。
* @param predir 前缀过滤器。
* @param recursive 是否递归搜索子目录。
* @return 返回一个包含所有匹配对象的结果列表。
*/
@Override
public List<Result<Item>> listObjects(String bucketName, String predir, boolean recursive) {
Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
.bucket(bucketName)
.prefix(predir)
.recursive(recursive)
.maxKeys(1000)
.build());
List<Result<Item>> list = new ArrayList<>();
results.forEach(list::add);
return list;
} /**
* 构建存储桶中对象的ComposeSource列表,用于复合对象操作。
*
* @param bucketName 存储桶名称。
* @param predir 前缀过滤器。
* @return 返回一个包含所有匹配对象的ComposeSource列表。
*/
@Override
public List<ComposeSource> listObjects(String bucketName, String predir) {
Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
.bucket(bucketName)
.prefix(predir)
.recursive(true)
.maxKeys(1000)
.build());
List<Result<Item>> list = new ArrayList<>();
results.forEach(list::add); List<ComposeSource> sources = new ArrayList<>();
for (Result<Item> itemResult : list) {
try {
sources.add(ComposeSource.builder().bucket(bucketName).object(itemResult.get().objectName()).build());
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
}
}
return sources;
} /**
* 复制一个对象到新的存储桶或新的对象名称。
*
* @param pastBucket 原始存储桶名称
* @param pastObject 原始对象名称
* @param newBucket 新的存储桶名称
* @param newObject 新的对象名称
* @return 如果复制成功,则返回true;否则返回false。
*/
@Override
public boolean copyObject(String pastBucket, String pastObject, String newBucket, String newObject) {
try {
ObjectWriteResponse response = minioClient.copyObject(
CopyObjectArgs.builder()
.bucket(newBucket)
.object(newObject)
.source(
CopySource.builder()
.bucket(pastBucket)
.object(pastObject)
.build())
.build());
} catch (ErrorResponseException e) {
e.printStackTrace();
return false;
} catch (InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
}
return true;
} /**
* 下载存储桶中的对象到本地文件系统。
*
* @param bucketName 存储桶名称
* @param objectName 对象名称
* @param targetPath 本地目标路径
* @return 如果下载成功,则返回true;否则返回false。
*/
@Override
public boolean downObject(String bucketName, String objectName, String targetPath) {
try {
if ("".equals(objectName) || null == objectName) {
throw new RuntimeException("检查电子文件FilePath是否为空,下载目标位置为:" + targetPath);
}
minioClient.downloadObject(
DownloadObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.filename(targetPath) //download local path
.build());
} catch (ErrorResponseException e) {
e.printStackTrace();
return false;
} catch (InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
}
return true;
} /**
* 获取存储桶中对象的预签名URL(GET方法)。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @param expire 过期时间(分钟)
* @return 预签名URL,如果发生错误则返回null。
*/
@Override
public String presignedURLofObject(String bucketName, String object, int expire) {
String url = null;
try {
url = minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(object)
.expiry(expire, TimeUnit.MINUTES)
.build());
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | XmlParserException | ServerException e) {
e.printStackTrace();
}
return url;
} /**
* 获取存储桶中对象的预签名URL,允许设置额外的查询参数。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @param expire 过期时间(分钟)
* @param map 额外的查询参数
* @return 预签名URL,如果发生错误则返回空字符串。
*/
@Override
public String presignedURLofObject(String bucketName, String object, int expire, Map<String, String> map) {
map.put("response-content-type", "application/json");
String presignedObjectUrl = "";
try {
presignedObjectUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.bucket(bucketName)
.object(object)
.method(Method.GET)
.expiry(expire, TimeUnit.MINUTES)
.extraQueryParams(map)
.build());
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | XmlParserException | ServerException e) {
e.printStackTrace();
}
return presignedObjectUrl;
} /**
* 删除存储桶中的对象。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @return 如果删除成功,则返回true;否则返回false。
*/
@Override
public boolean deleteObject(String bucketName, String object) {
try {
minioClient.removeObject(RemoveObjectArgs.builder()
.bucket(bucketName)
.object(object)
.build());
} catch (ErrorResponseException e) {
e.printStackTrace();
return false;
} catch (InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
}
return true;
} /**
* 将本地文件上传至MinIO存储桶。
*
* @param bucketName 存储桶名称
* @param targetObject 目标对象名称
* @param sourcePath 源文件路径
* @return 如果上传成功,则返回true;否则返回false。
*/
@Override
public boolean uploadObject(String bucketName, String targetObject, String sourcePath) {
try {
minioClient.uploadObject(UploadObjectArgs.builder()
.bucket(bucketName)
.object(targetObject)
.filename(sourcePath)
.build());
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
return false;
}
return true;
} /**
* 将输入流数据写入MinIO存储桶。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @param inputStream 输入流
* @return 如果写入成功,则返回true;否则返回false。
*/
@Override
public boolean putObject(String bucketName, String object, InputStream inputStream) {
try {
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(object)
.stream(inputStream, -1, 10485760)
.build());
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
return false;
}
return true;
} /**
* 将带有标签的输入流数据写入MinIO存储桶。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @param inputStream 输入流
* @param tags 对象标签
* @return 如果写入成功,则返回true;否则返回false。
*/
@Override
public boolean putObject(String bucketName, String object, InputStream inputStream, Map<String, String> tags) {
try {
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(object)
.stream(inputStream, -1, 10485760)
.tags(tags)
.build());
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
return false;
}
return true;
} /**
* 从MinIO存储桶获取对象。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @return 返回对象响应信息,如果发生错误则返回null。
*/
@Override
public GetObjectResponse getObject(String bucketName, String object) {
GetObjectResponse object1 = null;
try {
object1 = minioClient.getObject(GetObjectArgs.builder()
.bucket(bucketName)
.object(object)
.build());
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
}
return object1;
} /**
* 获取对象的标签信息。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @return 返回对象的标签映射,如果发生错误则返回null。
*/
@Override
public Map<String, String> getTags(String bucketName, String object) {
Map<String, String> map = new HashMap<>();
try {
Tags objectTags = minioClient.getObjectTags(GetObjectTagsArgs.builder()
.bucket(bucketName)
.object(object)
.build());
map = objectTags.get();
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
return null;
}
return map;
} /**
* 向对象添加或更新标签。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @param addTags 要添加或更新的标签映射
* @return 如果操作成功,则返回true;否则返回false。
*/
@Override
public boolean addTags(String bucketName, String object, Map<String, String> addTags) {
Map<String, String> oldtags = new HashMap<>();
Map<String, String> newTags = new HashMap<>();
try {
oldtags = getTags(bucketName, object);
if (oldtags.size() > 0) {
newTags.putAll(oldtags);
}
if (addTags != null && addTags.size() > 0) {
newTags.putAll(addTags);
}
minioClient.setObjectTags(SetObjectTagsArgs.builder()
.bucket(bucketName)
.object(object)
.tags(newTags)
.build());
return true;
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
return false;
}
} /**
* 获取对象的状态信息。
*
* @param bucketName 存储桶名称
* @param object 对象名称
* @return 返回对象状态信息,如果发生错误则返回null。
*/
@Override
public StatObjectResponse statObject(String bucketName, String object) {
StatObjectResponse statObject = null;
try {
statObject =
minioClient.statObject(
StatObjectArgs.builder()
.bucket(bucketName)
.object(object)
.build());
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
}
return statObject;
} /**
* 判断存储桶中是否存在指定对象。
*
* @param bucketName 存储桶名称
* @param objectName 对象名称
* @return 如果存在,则返回true;否则返回false。
*/
@Override
public boolean ifExistObject(String bucketName, String objectName) {
return listObjects(bucketName, objectName, true).size() >= 1;
} /**
* 从元数据存储桶中获取与特定对象相关的元数据对象名。
*
* @param objectName 原始对象名称
* @return 返回编码后的元数据对象名,如果没有找到对应的元数据则返回其文件名。
*/
@Override
public String getMetaNameFromOther(String objectName) {
String metaobject = "";
List<Result<Item>> results = listObjects(BucketName.METADATA, FileUtil.getPreMeta(objectName), true);
if (results.size() == 1) {
try {
metaobject = results.get(0).get().objectName();
Map<String, String> tags = getTags(BucketName.METADATA, metaobject);
String s = tags.get(ObjectTags.FILENAME);
// 解码后再编码以确保正确处理特殊字符
// URLDecoder.decode(s,"UTF-8");
return URLEncoder.encode(s, "UTF-8");
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
}
}
return FileUtil.getFileName(metaobject);
} /**
* 修改对象的标签信息。
*
* @param object 对象名称
* @param tag 新的标签值
* @return 如果修改成功,则返回true;否则返回false。
*/
@Override
public boolean changeTag(String object, String tag) {
try {
Map<String, String> map = minioClient.getObjectTags(GetObjectTagsArgs.builder()
.bucket(BucketName.METADATA)
.object(object)
.build()).get(); Map<String, String> map1 = new HashMap<>();
tag = tag + FileUtil.getSuffix(object);
map1.put(ObjectTags.FILENAME, tag);
map1.put(ObjectTags.OPERATOR, map.get(ObjectTags.OPERATOR)); minioClient.setObjectTags(SetObjectTagsArgs.builder()
.bucket(BucketName.METADATA)
.object(object)
.tags(map1)
.build());
return true;
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
return false;
}
} /**
* 设置存储桶的访问策略为公开访问。
*
* @param bucketName 存储桶名称
*/
@Override
public void BucketAccessPublic(String bucketName) {
String config = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:ListBucketMultipartUploads\",\"s3:GetBucketLocation\",\"s3:ListBucket\"],\"Resource\":[\"arn:aws:s3:::" + bucketName + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::" + bucketName + "/*\"]}]}";
try {
minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(config).build());
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
e.printStackTrace();
}
} }

MinIO使用记录的更多相关文章

  1. 免费版对象存储【minIO】CentOS部署实践记录 2021

    好久没写,记录一下 1.背景 之前一直用的七牛,不过是收费的,然后有些定制化需求,可能比较看重预算,然后就有了这篇开源方式:minio 2.简介 官方文档:http://docs.minio.org. ...

  2. Amazon S3 对象存储Java API操作记录(Minio与S3 SDK两种实现)

    缘起 今年(2023年) 2月的时候做了个适配Amazon S3对象存储接口的需求,由于4月份自学考试临近,一直在备考就拖着没总结记录下,开发联调过程中也出现过一些奇葩的问题,最近人刚从考试缓过来顺手 ...

  3. Linux记录-salt分析

    salt-master建立分组 如dn: 'L@dnxxx,dyyy' salt -N dn  state.apply  hadoop test=true salt -N dn  cmd.run  ' ...

  4. 利用 MinIO 轻松搭建静态资源服务

    目录 1 引言 2 MinIO 简介 3 MinIO 运行与静态资源使用 3.1 MinIO 获取 3.2 MinIO 启动与运行 3.2.1 前台简单启动 3.2.2 后台指定参数运行 3.2.3 ...

  5. MinIO 的分布式部署

    目录 1 前言 2 分布式存储可靠性常用方法 2.1 冗余 2.2 校验 3 MinIO存储机制 3.1 概念理解 3.2 纠删码EC(Erasure Code) 3.3 存储形式 4 部署实践 4. ...

  6. CentOS7安装MinIO教程,并在C#客户端WPF中实现监控上传进度

    MinIO的详细介绍可以参考官网(https://min.io/product/overview). 简单来说它是一个实现了AWS S3标准的100%开源的,可商用的( Apache V2 licen ...

  7. MinIO分布式集群的扩展方案及实现

    目录 一.命令行方式扩展 1. MinIO扩展集群支持的命令语法 2. 扩容示例 二.etcd扩展方案 1. 环境变量 2. 运行多个集群 3. 示例 相关链接 MinIO 支持两种扩展方式: 通过修 ...

  8. 【分布式技术专题】「OSS中间件系列」Minio的文件服务的存储模型及整合Java客户端访问的实战指南

    Minio的元数据 数据存储 MinIO对象存储系统没有元数据数据库,所有的操作都是对象级别的粒度的,这种做法的优势是: 个别对象的失效,不会溢出为更大级别的系统失效. 便于实现"强一致性& ...

  9. MinIO对接k8s使用

    文档地址:https://github.com/minio/operator/blob/master/README.md https://docs.min.io/minio/k8s/deploymen ...

  10. MinIO Client完全指南

    官方文档地址:http://docs.minio.org.cn/docs/master/minio-client-complete-guide 下载,添加云存储服务参考这篇文章:https://www ...

随机推荐

  1. ansible(7)--ansible的file模块

    1. file模块 功能:为被控端创建文件或目录,设定权限属性: 主要参数如下: 参数 说明 path 指定远程服务器的路径,也可以写成'dest','name' state 状态,可以将值设定为di ...

  2. List集合中获取重复元素

    一.方法1 ## 测试数据 List<String> words = Arrays.asList("a", "b", "c", ...

  3. java如何将JSONObject转成实体对象

    import com.google.gson.Gson; import org.json.JSONObject; // ... JSONObject json = new JSONObject(&qu ...

  4. three.js教程8-渲染器WebGLRenderer和前端UI界面

    1.html的UI交互界面与Canvas画布叠加 需求:把threejs Cavnas画布和HTML元素叠加布局,在canvas上添加按钮,通过按钮点击修改canvas场景. // canvas画布绝 ...

  5. linux下使用chattr创建一个连root都无法删除的文件

    一.关于chattr,lsattr 1.chattr 的作用:改变一个Linux文件系统上的文件属性. 2.chattr用来改变文件.目录的属性,lsattr用来查看文件.目录的属性. 3.chatt ...

  6. PHP 网络通信底层原理分析

    一.引言 我们日常的程序开发大多数都是以业务为主,很少会接触到底层逻辑.对于我们程序员来说,了解程序的底层运行逻辑,更有助于提升我们对程序的理解.我相信大多数的人,每天基本上都是完成业务需求.当然,完 ...

  7. C#调用微软api文本转语音

    目录 1.注册微软云服务,搭建文本转语音标准应用(每月500万字免费好像) 2.Visual studio使用nuget给程序安装Microsoft.CognitiveServices.Speech框 ...

  8. 009. gitlab备份和恢复

    gitlab备份 #1. 创建添加配置文件 vim /etc/gitlab/gitlab.rb 文件尾添加: gitlab_reils['backup_path'] = '/data/backup/g ...

  9. sourceTree .gitignore文件处理

    最近犯了个低级错误,在sourceTree中通过右键,直接将 工程文件加入到 ignore列表中.提交之后,别人拉下来代码一直显示报错 在意识到上面这个错误之后,一直想恢复却恢复不了,尝试从Sorce ...

  10. xhs全参xs,xt,xscommon逆向分析

    声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 目标网站 aHR0cHM6 ...