type
Post
status
Published
date
Jun 19, 2023
slug
summary
MinIO 是一个高性能、S3 兼容的对象存储。它专为大规模 AI/ML、数据湖和数据库工作负载而构建。它在本地和任何云(公共或私有)上以及从数据中心到边缘运行。
tags
开发
category
技术分享
icon
password
1.MinIO?1.1官网简介1.1.1企业级开源对象存储1.1.2真正的多云存储1.2特点1.3基础概念1.4纠错码1.5MinIO架构1.5.1单主机,单硬盘模式1.5.2单主机,多硬盘模式1.5.3多主机、多硬盘模式(分布式)2.服务搭建2.1服务端搭建2.2客户端搭建3.SpringBoot整合3.1MINIO SDK支持3.2初始化客户端3.3上传下载4.参考(引用)
存储可以分为块存储、对象存储和文件存储。在主流的分布式存储技术中,HDFS/GPFS/GFS 属于文件存储,Swift 属于对象存储,而 Ceph 可支持块存储、对象存储和文件存储,因此被称为统一存储。
存储类型的选择取决于应用场景和需求
- 块存储适用于需要低延迟访问的数据存储
- 文件存储适用于需要高吞吐量的数据存储
- 对象存储适用于海量非结构化数据的存储和管理
1.MinIO?
MinIO 是一个高性能、S3 兼容的对象存储。它专为大规模 AI/ML、数据湖和数据库工作负载而构建。它在本地和任何云(公共或私有)上以及从数据中心到边缘运行。
1.1官网简介
- 简单
Minio采用简单可靠的集群方案,摒弃复杂的大规模的集群调度管理,减少风险与性能瓶颈,聚焦产品的核心功能,打造高可用的集群、灵活的扩展能力以及超过的性能。建立众多的中小规模、易管理的集群,支持跨数据中心将多个集群聚合成超大资源池,而非直接采用大规模、统一管理的分布式集群。
- 高性能
MinIO 是世界上最快的对象存储,已发布的 GET/PUT 结果在 32 个 NVMe 驱动器节点和 100Gbe 网络上超过 325 GiB/秒和 165 GiB/秒。
- Kubernetes-原生
通过原生 Kubernetes 运营商集成,MinIO 支持公共云、私有云和边缘云上的所有主要 Kubernetes 发行版。
1.1.1企业级开源对象存储
- 对象存储的主动复制
对象存储的主动多站点复制是关键任务生产环境的关键要求。MinIO 提供桶级粒度,并支持同步和近同步复制,具体取决于架构选择和数据更改率。

- 加密
MinIO 通过最高级别的加密以及广泛的优化提供更多功能,这些优化几乎消除了通常与存储加密操作相关的开销。

- 自动化数据管理界面
MinIO 提供了一套选项来涵盖数据驱动型企业中的每个角色,例如图形用户界面 (GUI)、命令行界面 (CLI) 和应用程序编程界面 (API)。MinIO 的数据管理接口功能可互换,以提供细粒度、高性能和可扩展的对象存储管理。

- 桶和对象不变性

- AWS S3 兼容对象存储

1.1.2真正的多云存储
- 公有云
- 私有云
- 边缘云
1.2特点
- 数据保护——分布式Minio采用 纠删码来防范多个节点宕机和位衰减bit rot。分布式Minio至少需要4个硬盘,使用分布式Minio自动引入了纠删码功能。、
- 高可用——单机Minio服务存在单点故障,相反,如果是一个有N块硬盘的分布式Minio,只要有N/2硬盘在线,你的数据就是安全的。不过你需要至少有N/2+1个硬盘来创建新的对象。
- 一致性——Minio在分布式和单机模式下,所有读写操作都严格遵守read-after-write一致性模型。
优点
- 部署简单,一个二进制文件(minio)即是一切,还可以支持各种平台
- 支持海量存储,可以按zone扩展,支持单个对象最大5TB
- 低冗余且磁盘损坏高容忍,标准且最高的数据冗余系数为2(即存储一个1M的数据对象,实际占用磁盘空间为2M)。但在任意n/2块disk损坏的情况下依然可以读出数据(n为一个纠删码集合中的disk数量)。并且这种损坏恢复是基于单个对象的,而不是基于整个存储卷的
- 读写性能优异
1.3基础概念
S3——Simple Storage Service,简单存储服务,这个概念是Amazon在2006年推出的,对象存储就是从那个时候诞生的。S3提供了一个简单Web服务接口,可用于随时在Web上的任何位置存储和检索任何数量的数据。
Object——存储到 Minio 的基本对象,如文件、字节流…
Bucket——用来存储 Object 的逻辑空间。每个 Bucket 之间的数据是相互隔离的。
Drive——部署 Minio 时设置的磁盘,Minio 中所有的对象数据都会存储在 Drive 里。
Set——一组 Drive 的集合,分布式部署根据集群规模自动划分一个或多个 Set ,每个 Set 中的 Drive 分布在不同位置。- 一个对象存储在一个Set上
- 一个集群划分为多个Set
- 一个Set包含的Drive数量是固定的,默认由系统根据集群规模自动计算得出
- 一个SET中的Drive尽可能分布在不同的节点上
Set /Drive 的关系
- Set /Drive 这两个概念是 MINIO 里面最重要的两个概念,一个对象最终是存储在 Set 上面的。
- Set 是另外一个概念,Set 是一组 Drive 的集合,图中,所有蓝色、橙色背景的Drive(硬盘)的就组成了一个 Set。

1.4纠错码
纠删码(Erasure Code)简称EC,是一种数据保护方法,它将数据分割成片段,把冗余数据块扩展、编码,并将其存储在不同的位置,比如磁盘、存储节点或者其它地理位置。
- 纠删码是一种恢复丢失和损坏数据的数学算法,目前,纠删码技术在分布式存储系统中的应用主要有三类,阵列纠删码(Array Code: RAID5、RAID6等)、RS(Reed-Solomon)里德-所罗门类纠删码和LDPC(LowDensity Parity Check Code)低密度奇偶校验纠删码。
- Erasure Code是一种编码技术,它可以将n份原始数据,增加m份校验数据,并能通过n+m份中的任意n份原始数据,还原为原始数据。
- 即如果有任意小于等于m份的校验数据失效,仍然能通过剩下的数据还原出来。
- Minio采用Reed-Solomon code将对象拆分成N/2数据和N/2 奇偶校验块。
- 在同一集群内,MinIO 自己会自动生成若干纠删组(Set),用于分布存放桶数据。一个纠删组中的一定数量的磁盘发生的故障(故障磁盘的数量小于等于校验盘的数量),通过纠删码校验算法可以恢复出正确的数据。
1.5MinIO架构
1.5.1单主机,单硬盘模式

1.5.2单主机,多硬盘模式

1.5.3多主机、多硬盘模式(分布式)

该模式是Minio服务最常用的架构,通过共享一个access_key和secret_key,在多台服务器上搭建服务,且数据分散在多块(大于4块,无上限)磁盘上,提供了较为强大的数据冗余机制(Reed-Solomon纠删码)。
2.服务搭建
2.1服务端搭建
下载可执行文件,MacOS
授权
wget https://dl.min.io/server/minio/release/darwin-amd64/minio chmod +x minio
启动服务端
- 启动脚本前需加上
MINIO_ROOT_USER=xxx初始化默认的账号
- 启动脚本前需加上
MINIO_ROOT_PASSWORD=xxx初始化默认的密码
- 后台启动
./minio server /home/minio/data启动服务端,将文件存储至/home/minio/data,文件存储目录不能是根目录/的挂载点,也不能是/root的挂载点
--console-address ":9001",初始化默认的端口和ip
>/opt/minio/minio.log 2>&1 &,初始化日志记录位置
MINIO_ROOT_USER=sinosoft MINIO_ROOT_PASSWORD=password nohup ./minio server /home/minio/data --address ":9000" --console-address ":9001" > /opt/minio/minio.log 2>&1 &
查找mino进程
ps -ef | grep minio # 访问 http://127.0.0.1:9001/

2.2客户端搭建
下载可执行文件,MacOS
授权
wget https://dl.min.io/client/mc/release/darwin-amd64/mc chmod +x mc
设置桶的读写权限
./mc config host add minio http://127.0.0.1:9000 minioadmin minioadmin --api S3v4 ./mc policy set public test # 查看信息 ./mc admin info minio
3.SpringBoot整合
3.1MINIO SDK支持
<dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.5.3</version> </dependency>
3.2初始化客户端
package live.javaer.minio.client.impl; import io.minio.*; import io.minio.http.Method; import io.minio.messages.Bucket; import live.javaer.minio.client.IMinioUtil; import live.javaer.minio.config.MinioConfig; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.io.InputStream; import java.util.List; @Slf4j @Service public class MinioUtil implements IMinioUtil { private static final String ACCESS_KEY = "FrQpw2FKNqTz1mUCrP5h"; private static final String SECRET_KEY = "oFqQSDLBpkRoO3H4JewV2ARegFt55fcmXf5PejTK"; public static void main(String[] args) throws Exception { io.minio.MinioClient minioClient = io.minio.MinioClient.builder() .endpoint("http://192.168.1.107:9000") .credentials(ACCESS_KEY, SECRET_KEY) .build(); // bucket exist? boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket("test1").build()); log.info("exist: {}", exists); } private MinioClient minioClient; @Resource private MinioConfig config; @PostConstruct public void init() { try { if (minioClient == null) { log.info("init minio client..."); minioClient = MinioClient .builder() .endpoint(config.getEndpoint()) .credentials(config.accessKey, config.secretKey) .build(); log.info("init bucket..."); createBucket(config.bucketName); } } catch (Exception e) { log.error("init err", e); } } public String getRootUrl() { return config.getEndpoint() + config.getSeparator() + config.getBucketName() + config.getSeparator(); } public boolean existBucket(String bucketName) throws Exception { return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); } public void createBucket(String bucketName) throws Exception { if (!existBucket(bucketName)) { minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); } } public List<Bucket> getAllBuckets() throws Exception { return minioClient.listBuckets(); } public boolean isObjectExist(String bucketName, String objectName) { boolean exist = true; try { minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build()); } catch (Exception e) { exist = false; } return exist; } public InputStream getObject(String bucketName, String objectName) throws Exception { return minioClient.getObject(GetObjectArgs.builder() .bucket(bucketName) .object(objectName) .build()); } public InputStream getObject(String bucketName, String objectName, long offset, long length) throws Exception { return minioClient.getObject(GetObjectArgs.builder() .bucket(bucketName) .object(objectName) .offset(offset) .length(length) .build()); } public ObjectWriteResponse uploadFile(String bukectName, MultipartFile file, String objectName, String contentType) throws Exception { InputStream inputStream = file.getInputStream(); return minioClient.putObject(PutObjectArgs.builder() .bucket(bukectName) .object(objectName) .contentType(contentType) .stream(inputStream, inputStream.available(), -1) .build()); } public ObjectWriteResponse uploadFile(String bucketName, String objectName, String fileName) throws Exception { return minioClient.uploadObject(UploadObjectArgs.builder() .bucket(bucketName) .object(objectName) .filename(fileName) .build()); } public ObjectWriteResponse uploadFile(String bucketName, String objectName, InputStream inputStream) throws Exception { return minioClient.putObject(PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(inputStream, inputStream.available(), -1) .build()); } public String getPresignedObjectUrl(String bucketName, String objectName) throws Exception { GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder() .bucket(bucketName) .object(objectName) .method(Method.GET).build(); return minioClient.getPresignedObjectUrl(args); } }
3.3上传下载
package live.javaer.minio.web; import io.minio.ObjectWriteResponse; import live.javaer.minio.client.impl.MinioUtil; import live.javaer.minio.config.MinioConfig; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.*; @Slf4j @RestController public class MinioController { @Resource private MinioUtil minioUtil; @Resource private MinioConfig config; @PostMapping("/upload") public String upload(@RequestParam("file") MultipartFile multipartFile) throws Exception { String fileName = multipartFile.getOriginalFilename(); ObjectWriteResponse response = minioUtil.uploadFile(config.getBucketName(), multipartFile, fileName, "multipart/form-data"); String url = minioUtil.getRootUrl() + fileName; log.info("upload success, response: {}, url: {}", response, url); return url; } @GetMapping("/download") public String download(@RequestParam("bucket_name") String bucketName, @RequestParam("object_name") String objectName, HttpServletResponse response) throws Exception { response.setContentType("application/octet-stream"); response.setCharacterEncoding("utf-8"); response.setHeader("Content-Disposition", "attachment;filename=" + objectName); try (BufferedInputStream bis = new BufferedInputStream(minioUtil.getObject(bucketName, objectName))) { OutputStream os = response.getOutputStream(); int i = 0; byte[] buff = new byte[1024]; while ((i = bis.read(buff)) != -1) { os.write(buff, 0, i); os.flush(); } } catch (Exception e) { } return "success"; } @GetMapping("/exist") public boolean existObject(@RequestParam("bucket_name") String bucketName, @RequestParam("object_name") String objectName) { return minioUtil.isObjectExist(bucketName, objectName); } }
4.参考(引用)
源码
