基本概念
MinIO 是一个开源的对象存储服务器,它允许你使用 AWS S3 兼容的 API 存储和检索任意类型的数据。
对象存储: MinIO 提供了一个简单而强大的对象存储解决方案。你可以使用 MinIO 存储各种类型的文件、图片、视频、文档等。
S3 兼容性: MinIO 实现了与 Amazon S3 兼容的 API,因此你可以使用与 S3 相似的方式与 MinIO 进行交互。这意味着你可以使用支持 S3 的应用程序或工具(如AWS SDK)连接到 MinIO,并对存储在 MinIO 中的对象进行操作。
分布式部署:MinIO 支持分布式部署,可以通过添加更多的节点来扩展存储容量和性能。
安全性:MinIO 提供了许多安全功能,包括数据加密、访问控制和身份验证。
高可用性:通过使用分布式部署和故障转移机制,MinIO 可以提供高可用性,确保你的数据始终可访问。
首先需要先了解一下这个minio是干嘛的,在开发的时候对我们开发人员使用来说就是暴露Api接口供我们使用,按照指定的方式将文件直接上传即可,后面所有的事情都是minio去帮我们在做,例如对文件分块存储等。
当然,也可以适当的去了解一下大概得原理
存储原理:
他的存储原理也是分块存储,意思是会将一份文件存储到多个块,每个块的大小默认是64mb。下载的时候会拿到所有的块,组成一个完整的文件,如果文件小于64mb则是存储在一个块里面
从安全性角度来看,将文件分成多个块并存储在不同的位置,可以增加数据的冗余备份和容错能力。即使某个节点或块发生故障,仍然可以通过其他节点或块来恢复数据,确保数据的可靠性和持久性。同时,由于拿到部分块无法还原完整的文件,这种分块存储机制也增加了数据的安全性,防止未经授权的访问和泄露。
从性能角度来看,将文件切分成多个块,并行读取多个块可以提高数据的读取效率。多个线程或者并发请求可以同时读取不同的块,减少了读取的等待时间。此外,由于每个块的大小相对较小,可以更好地利用网络带宽和存储设备的性能,提高数据的传输速度和处理效率。
因此,MinIO 的分块存储机制既提升了数据的安全性,又提高了数据的读取性能,适应了现代大规模数据存储和处理的需求。
安装Minio(Docker安装)
不会Docker的话可以去查一下安装包的安装方法,别人的安装教程 Linux安装Minio
当然也可以直接使用Docker,因为就几个命令,也不难
Docker有了并且基本会用的,可以直接看拉取镜像和创建容器的那部分了
安装Docker
-
yum -y update
用于更新操作系统中已安装软件包的命令
具体来说,yum -y update 命令会检查您的操作系统中已安装软件包的最新版本,并将其升级到最新版本。这包括操作系统内核、系统工具、库文件、应用程序等。通过执行此命令,您可以获得最新的功能、安全性和性能改进,并修复已知的漏洞和错误。 -
安装Docker所需的依赖项
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
-
设置Docker存储库(可国内阿里云的镜像)
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
-
安装Docker
sudo yum install -y docker-ce docker-ce-cli containerd.io
-
启动验证安装成功与否
- 启动
sudo systemctl start docker
- 设置开机自启
sudo systemctl enable docker
- 验证
sudo docker run hello-world
这样就是成功
- 启动
-
拉取Minio
docker pull minio/minio
-
启动容器
创建数据卷用于挂载数据(因为启动的容器,要修改以及持久化的问题,所以需要挂载到我们熟悉的地方,例如修改配置文件需要进入容器内部,或者该容器产生的数据我们删除容器之后会找不到或消失)
mkdir /usr/local/software/minio/data /usr/local/software/minio/config
启动命令
docker run --name minio -p 9000:9000 -p 9001:9001 -v /usr/local/software/minio/data/:/data -v /usr/local/software/minio/config/:/root/.minio -d --restart=always -e "MINIO_ACCESS_KEY=admin" -e "MINIO_SECRET_KEY=admin123" minio/minio server /data --console-address ":9000" --address ":9001"
说明:--console-address ":9000"--address ":9001"分别表示控制台的端口和Minio服务的端口
-e "MINIO_ACCESS_KEY=admin" 账号 -e "MINIO_SECRET_KEY=admin123" 密码
-
验证是否成功
打开控制台 ip + --console-address 的端口(9000)看到下面的页面就成功了,输入run命令中的账号密码即可
术语
存储桶(Bucket):MinIO 中的存储单元,类似于文件系统中的文件夹。存储桶用于组织和管理对象。
对象(Object):存储在存储桶中的基本数据单元。对象可以是任何类型的文件,例如图片、视频、文档等。
区域(Region):MinIO 的多区域部署方案中,指的是物理位置或数据中心,用于提供高可用性和容错性。
签名(Signature):在 MinIO 客户端与服务器之间进行身份验证和请求的安全传输过程中使用的密钥。
策略(Policy):用于定义访问控制规则的配置文件。策略决定了谁可以执行哪些操作以及对哪些资源具有权限。
生命周期(Lifecycle):用于管理对象在特定时间点自动转换存储类别、删除或其他操作的规则。
版本控制(Versioning):一项功能,允许在存储桶中保留对象的多个版本,以便进行版本控制和恢复。
分片上传(Multipart Upload):将大型对象分割为较小的块进行并行上传,提高上传效率和可靠性。
这些术语和概念是 MinIO 中常用的,了解它们可以帮助你更好地理解和使用 MinIO 存储服务。
Spring Boot集成Minio
整合的环境是Spring Boot 3.x版本,jdk17,Minio 8.5.5版本的
- 引入依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.5</version>
</dependency>
-
Minio SDK 简述
集成minio主要是通过minioclient实现的。
因此我们需要先创建Minio Client对象,是的你没听错,需要手动创建,他不像Redis那种有Spring starter的在application.yml里面配置minio服务器地址
创建minioconfig,注入minioclient的bean到Spring中。package com.example.household_supplies.config; import io.minio.MinioClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MinIOConfig { @Value("${minio.url}") private String url; @Value("${minio.access-key}") private String accessKey; @Value("${minio.secret-key}") private String secretKey; @Bean public MinioClient minioClient() { return MinioClient.builder() .endpoint(url) .credentials(accessKey, secretKey) .build(); } }
-
MinioClient 使用
-
常见内置方法说明
public StatObjectResponse statObject(StatObjectArgs args) // 从Minio获取对象的元数据信息 public GetObjectResponse getObject(GetObjectArgs args) // 从Minio获取文件对象的文件流 public void downloadObject(DownloadObjectArgs args) // 从Minio下载到指定文件 public ObjectWriteResponse copyObject(CopyObjectArgs args) // 在Minio的不同的bucket之间copy数据 public String getPresignedObjectUrl(GetPresignedObjectUrlArgs args) // 生成临时的访问url public void removeObject(RemoveObjectArgs args) // 从Minio中删除文件 public ObjectWriteResponse putObject(PutObjectArgs args) // 上传到Minio文件中 public boolean bucketExists(BucketExistsArgs args) // 检查桶是否存在 public List<Bucket> listBuckets(ListBucketsArgs args) // 获取所有存储桶 public void makeBucket(MakeBucketArgs args) // 创建指定的桶
-
使用(写成一个工具类了)
package com.example.household_supplies.util; import com.example.household_supplies.exception.BizAssertUtils; import io.minio.*; import io.minio.errors.*; import io.minio.http.Method; import io.minio.messages.Bucket; import io.minio.messages.Source; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * @Classname MinioStroage * @Description TODO * @Version 1.0.0 * @Created by wlh12 */ @Repository @Slf4j public class MinioStroage { @Autowired private MinioClient minioClient; /** * 上传 * * @param file * @param bucket * @return */ public String upload(MultipartFile file, String bucket) { String fileName = buildFileName(file.getOriginalFilename()); try (InputStream inputStream = file.getInputStream()) { if (!bucketExists(bucket)) { createBucket(bucket); } PutObjectArgs putObjectArgs = PutObjectArgs .builder() .bucket(bucket) .object(fileName) .stream(inputStream, inputStream.available(), -1) .build(); minioClient.putObject(putObjectArgs); } catch (Exception e) { log.error("minio上传失败:", e); throw new RuntimeException(e); } return fileName; } /** * 批量上传 * * @param files * @param bucket * @return */ public String[] upload(MultipartFile[] files, String bucket) { String[] strings = new String[files.length]; int i = 0; for (MultipartFile file : files) { String res = upload(file, bucket); strings[i++] = res; } return strings; } /** * 批量下载 * * @param fileNames * @param bucket * @return */ public List<byte[]> download(String[] fileNames, String bucket) { List<byte[]> list = new ArrayList<>(); for (String fileName : fileNames) { byte[] download = download(fileName, bucket); list.add(download); } return list; } /** * 批量删除 * * @param fileNames * @param bucket */ public void delete(String[] fileNames, String bucket) { for (String fileName : fileNames) { delete(fileName, bucket); } } /** * 下载 * * @param fileName * @param bucket * @return */ public byte[] download(String fileName, String bucket) { BizAssertUtils.isTrue(bucketExists(bucket), "该文件不存在!"); GetObjectArgs getObjectArgs = GetObjectArgs.builder() .bucket(bucket) .object(fileName) .build(); try (InputStream inputStream = minioClient.getObject(getObjectArgs)) { return inputStream.readAllBytes(); } catch (Exception e) { log.error("minio获取文件失败:", e); throw new RuntimeException(e); } } /** * 将指定文件下载保存到本机file * * @param filename * @param bucket * @param file */ public void saveLocal(String filename, String bucket, File file) { try { DownloadObjectArgs downloadObjectArgs = DownloadObjectArgs.builder() .bucket(bucket) .object(filename) .filename(file.getPath()) .build(); minioClient.downloadObject(downloadObjectArgs); } catch (Exception e) { log.error("从Minio保存本地失败:", e); throw new RuntimeException(e); } } /** * 删除文件 * * @param fileName * @param bucket */ public void delete(String fileName, String bucket) { BizAssertUtils.isTrue(bucketExists(bucket), "该文件不存在!"); RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder() .bucket(bucket) .object(fileName) .build(); try { minioClient.removeObject(removeObjectArgs); } catch (Exception e) { log.error("minio删除文件失败:", e); throw new RuntimeException(e); } } /** * 获取文件对象的信息(不下载文件),例如标签,大小,最后修改时间,保留时间信息等等。 * * @param filename * @param bucket * @return */ public StatObjectResponse getStatObjectInfo(String filename, String bucket) { try { StatObjectArgs statObjectArgs = StatObjectArgs.builder() .bucket(bucket) .object(filename) .build(); return minioClient.statObject(statObjectArgs); } catch (Exception e) { log.error("minio获取文件信息失败:", e); throw new RuntimeException(e); } } /** * 将指定文件从srcBucket复制到tarBucket中 * @param filename * @param srcBucket * @param tarBucket */ public void copyToBucket(String filename, String srcBucket, String tarBucket) { try { boolean b = bucketExists(srcBucket); BizAssertUtils.isTrue(b, "srcBucket 不存在"); b = bucketExists(tarBucket); if (!b) { createBucket(tarBucket); } CopyObjectArgs copyObjectArgs = CopyObjectArgs.builder() .bucket(tarBucket) .object(filename) .source( CopySource.builder() .bucket(srcBucket) .object(filename) .build() ) .build(); minioClient.copyObject(copyObjectArgs); } catch (Exception e) { log.error("minio copy失败:", e); throw new RuntimeException(e); } } /** * 获取该文件的预览地址 * @param filename * @param bucket * @return */ public String previewUrl(String filename, String bucket) { try { GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder() .bucket(bucket) .object(filename) .method(Method.GET) // 设置预览的请求方法 .build(); return minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs); } catch (Exception e) { log.error("minio获取预览url失败:", e); throw new RuntimeException(e); } } /** * 检查bucket桶是否存在 * * @param bucket * @return */ public boolean bucketExists(String bucket) { BucketExistsArgs existsArgs = BucketExistsArgs .builder() .bucket(bucket) .build(); try { return minioClient.bucketExists(existsArgs); } catch (Exception e) { log.error("检查bucket失败:", e); throw new RuntimeException(e); } } /** * 创建bucket桶 * * @param bucket */ public void createBucket(String bucket) { MakeBucketArgs makeBucketArgs = MakeBucketArgs .builder() .bucket(bucket) .build(); try { minioClient.makeBucket(makeBucketArgs); } catch (Exception e) { log.error("创建bucket失败:", e); throw new RuntimeException(e); } } /** * 获取当前Minio服务器的所有存储桶 * * @return */ public List<Bucket> getBucketList() { try { return minioClient.listBuckets(); } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) { log.error("获取bucket列表失败:", e); throw new RuntimeException(e); } } /** * 获取当前Minio服务器的所有存储桶 * * @param queryParam 额外的查询参数 * @param headers 额外的请求头信息 * @return */ public List<Bucket> getBucketList(Map<String, String> queryParam, Map<String, String> headers) { try { ListBucketsArgs.Builder builder = ListBucketsArgs.builder(); if (queryParam != null && !queryParam.isEmpty()) { builder.extraQueryParams(queryParam); } if (headers != null && !headers.isEmpty()) { builder.extraHeaders(headers); } ListBucketsArgs listBucketsArgs = builder.build(); return minioClient.listBuckets(listBucketsArgs); } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) { throw new RuntimeException(e); } } /** * 用于生成文件名称 * * @param fileName * @return */ public String buildFileName(String fileName) { int index = fileName.lastIndexOf("."); String suffix = fileName.substring(index); return System.currentTimeMillis() + CodeUtil.createNumberCode(3) + suffix; } }
-
到了这,Minio的基本使用就结束了,更多的可以去看MinioClient里面的示例