MinIO-对象存储简单使用

发布时间 2023-07-01 15:41:49作者: 永恒&

MinIO

1. 基础概念

  • Object:存储到minio的基本对象,如文件,字节流,Anything...
  • Bucket:用来存储Object的逻辑空间。每个Bucket之间的数据是相互隔离的。对于客户端而言,就相当于一个存放文件的顶层文件夹。
  • Driver:即存储数据的磁盘,在minio启动时,以参数的方式传入。MinIO中所有的对象都会存储在Drive里。
  • Set:即一组Drive的集合,分布式部署根据集群规模自动划分一个或者set,每个set中的drive分布在不同的位置。一个对象存储在一个Set上。
    • 一个对象存储在一个Set上
    • 一个集群划分为多个set
    • 一个Set包含的Drive数量是固定的,默认由系统根据集群规模自动计算得出
    • 一个Set中的Drive尽可能分布在不同的节点上

2. 纠删码 EC(Erasure Code)

MinIO使用纠删码机制来保证高可靠性,使用highwayhash 来处理数据损坏 (2 Protection)。纠删码,可以通过数学计算,把丢失的数据进行还原,它可以将n份原始数据,增加m份数据,并能通过n+m份中的任意n份数据,还原为原始数据。即如果有任意小于等于m份的数据失效,仍能通过剩下的数据还原出来。

3. 单机部署

中文文档的docker部署有问题,没有暴露控制台的端口。

单机部署的命令

基于centos7的部署

# 下载
wget https://dl.min.io/server/minio/release/linux-amd64/archive/minio-20230616024106.0.0.x86_64.rpm -O minio.rpm
sudo dnf install minio.rpm
# 指定 控制台的root用户名和密码
export MINIO_ROOT_USER=admin123
export MINIO_ROOT_PASSWORD=admin123
# 指定 控制台暴露的端口,默认使用动态的端口,并指定数据存放的目录
./minio server --console-address ":50000" /data/minio-data
# 如果上述命令运行成功,可以通过控制台端口50000访问minio后台,证明运行时没问题的,就停掉。然后用下面的命令后台运行minio
nohup  ./minio server --console-address ":50000" /data/minio-data &

image-20230618191623676

image-20230618191652757

image-20230618191551966

控制台上传一个文件可以在服务器对应的目录中找到文件,服务正常运行。

基于docker的部署

docker 部署命令

docker pull docker://minio/minio

### 新版
docker run --name minio \
-p 9000:9000 \
-p 9999:9999 \
-d --restart=always \
-e "MINIO_ROOT_USER=admin123" \
-e "MINIO_ROOT_PASSWORD=admin123" \
-v /home/smy/minio-data/data:/data \
-v /home/smy/minio-data/config:/root/.minio \
minio/minio server /data \
--console-address '0.0.0.0:9999'

4. SpringBoot整合minio

遇到的问题

image-20230619153814425

解决方法:

修改minio的依赖的okhttp的依赖版本

        <!--minio-->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.2.1</version>
            <exclusions>
                <exclusion>
                    <groupId>com.squareup.okhttp3</groupId>
                    <artifactId>okhttp</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.jetbrains.kotlin</groupId>
                    <artifactId>kotlin-stdlib</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.14.9</version>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib</artifactId>
            <version>1.3.70</version>
        </dependency>

https://juejin.cn/post/7209110611858309179

5. JAVA API 操作 MinIO

官方文档

主要包括桶操作和文件操作:

  • 客户端的创建
  • 桶 增删改查
  • 文件 上传 下载 删除(过期删除)

image-20230621213958331

minio上传的大小限制,分片上传最小5MB,最大5GB;整个上传最大5TB。并且当不提供objectSize的时候,必须提供partSize(适合上传文件大小未知的场景)。

image-20230621223113936

putObject 上传文件后的返回对象

6. 工具类

image-20230701152015912

配置类

package org.example.config;

/**
 * @author myS
 * @description: MinioConfig
 * @date 2023/6/21 17:21
 */
public class MinioConfig {
    // 端点  单节点
    public static String END_POINT = "http://192.168.1.244:9000";
    // accessKey
    public static String ACCESS_KEY = "admin123";
    // secretKey
    public static String SECRET_KEY = "admin123";
    // bucketName 默认操作的bucket
    public static String BUCKET_NAME = "default";
}

工具类

结合实际业务做拓展

package org.example.utils;

import io.minio.*;
import io.minio.messages.*;
import org.example.config.MinioConfig;

import javax.annotation.Nonnull;
import java.io.InputStream;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * @author myS
 * @description: MinioUtils
 * @date 2023/6/21 17:20
 */
public class MinioUtils {
    public static MinioClient minioClient = null;

    static {
        minioClient = MinioClient.builder()
                .endpoint(MinioConfig.END_POINT)
                .credentials(MinioConfig.ACCESS_KEY, MinioConfig.SECRET_KEY)
                .build();
    }

    // 1 桶操作

    /**
     * 1 根据 bucketName 判断 bucket 是否存在
     * @param bucketName
     * @return
     */
    public static boolean bucketExists(@Nonnull String bucketName) {
        try {
            return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 2 创建bucket, 使用时不需要提前判断 bucket 是否存在
     * @param bucketName
     * @return
     */
    public static boolean makeBucket(@Nonnull String bucketName) {
        try {
            // 当bucket不存在的时候,创建这个bucket
            if (bucketExists(bucketName)) {
                minioClient.makeBucket(MakeBucketArgs.builder()
                        .bucket(bucketName)
                        .build());
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 3 根据bucketName删除一个bucket
     * @param bucketName
     * @return
     */
    public static boolean removeBucket(@Nonnull String bucketName) {
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 4 获取所有的 bucket, 使用时需要判断返回值是否为null,再进行后续操作
     * @return
     */
    public static List<Bucket> getAllBuckets() {
        try {
            List<Bucket> buckets = minioClient.listBuckets();
            return buckets;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 5 设置 bucket 的声明周期,设置对象过期时间
     * @param bucketName 指定bucket,默认是对指定的这一个桶生效
     * @param expiredDays 过期时间 单位:天
     * @return
     */
    public static boolean setBucketLifecycle (@Nonnull String bucketName, @Nonnull Integer expiredDays) {
        // 创建一个带过期时间规则的配置config
        List<LifecycleRule> rules = new LinkedList<>();
        rules.add(
                new LifecycleRule(
                        Status.ENABLED,
                        null,
                        new Expiration((ZonedDateTime) null, expiredDays, null),
                        new RuleFilter("search"), // bucket下一级的目录 
                        "expiredRule_1",
                        null,
                        null,
                        null));
        LifecycleConfiguration config = new LifecycleConfiguration(rules);

        // 根据 config的配置,创建一个名称为 my-bucketname 的 bucket
        try {
            minioClient.setBucketLifecycle(
                    SetBucketLifecycleArgs.builder().bucket(bucketName).config(config).build());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    // 二 文件操作

    /**
     * 上传文件并返回是否上传成功
     * @param bucketName
     * @param inputStream 待上传文件的输入流
     * @return
     */
    public static boolean upload (@Nonnull InputStream inputStream, @Nonnull String bucketName, @Nonnull String objectName) {
        try {
            ObjectWriteResponse objectWriteResponse = minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .stream(inputStream, -1, 1024 * 1024 * 10L) // 分片上传 10M
                            .object(objectName)
                            .build());
            System.out.println(objectWriteResponse.region());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    /**
     * 根据bucket和objectName返回一个minio中的对象的输入流
     * 因为可能为null,所以使用前需要先判null,避免发生其他错误
     * @param bucketName
     * @param objectName
     * @return
     */
    public static InputStream download (@Nonnull String bucketName, @Nonnull String objectName) {
        try {
            return minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 删除指定bucket下的名为objectName的对象
     * @param bucketName
     * @param objectName
     * @return
     */
    public static boolean removeObject(@Nonnull String bucketName, @Nonnull String objectName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                    .build());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 获取 当前bucket下的所有对象(只会列出一层)
     * @param bucketName
     * @return
     */
    public static List<String> getAllObjects (@Nonnull String bucketName) {
        List<String> allObjects = new ArrayList<>();
        try{
            Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
                    .bucket(bucketName)
                    .build());
            results.forEach(item -> {
                Item item1 = null;
                try {
                    item1 = item.get();
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new RuntimeException(e);
                }
                allObjects.add(item1.objectName());
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
        return allObjects;
    }

}

工具类测试类

package org.example;

import io.minio.MinioClient;
import io.minio.messages.Bucket;
import org.example.utils.MinioUtils;
import org.junit.Assert;
import org.junit.Test;

import java.io.*;
import java.nio.file.Files;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

/**
 * @author myS
 * @description: TODO
 * @date 2023/6/21 21:47
 */
public class MinioTest {

//    public static MinioClient minioClient = MinioUtils.minioClient;

    /**
     * 测试 getAllBuckets 只能列出最外层的bucket,里层嵌套的bucket不能列出
     * logs
     * my-bucketname
     * test
     */
    @Test
    public void testList () {
        List<Bucket> test = MinioUtils.getAllBuckets();
        test.forEach(item -> {
            System.out.println(item.name());
        });
    }

    /**
     * 因为 inputStream 无法获取到 源文件的名字,所以需要指定一个 objectName,
     * 这个参数如果以 / 开头,就会新建一层目录;
     * 如果不以 / 开头,就会直接在 bucketName 下上传文件
     * 如果文件名和目录名 重复,后面的会覆盖前面的,前面的会在删除后面的之后显示出来  ****
     * @throws FileNotFoundException
     */
    @Test
    public void testUpload () throws FileNotFoundException {
        File file = new File("C:\\Users\\10170\\Desktop\\desktop\\cnblog\\待整理.md");
        InputStream inputStream = new FileInputStream(file);
        // 上传的文件路径: test/hdhd
        boolean upload = MinioUtils.upload(inputStream,"test","/hdhd");
        System.out.println(upload);
        // 上传的文件路径: test/hdhd/待整理.md
        boolean upload1 = MinioUtils.upload(inputStream,"test","/hdhd/"+file.getName());
        System.out.println(upload1);
    }

    /**
     * 测试 minio 的下载函数
     * 函数返回一个输入流,表示从文件写入内存中,用完close释放
     * 服务器程序拿到这个输入流之后,可以通过网络返回给前端
     * @throws IOException
     */
    @Test
    public void testDownload () throws IOException {
        InputStream stream = MinioUtils.download("logs", "b50aa3fcb9fa4f64a4e295b7a996aa9a/events.json");
        // stream 判 null
        if (Objects.isNull(stream)) {
            System.out.println("文件流为null!!!!! 下载失败");
            return ;
        }

        // 读取流 并下载
        File targetFile = new File("events.json");
        FileOutputStream fileOutputStream = new FileOutputStream(targetFile);
        byte[] bytes = new byte[1024];
        int length;
        while ((length = stream.read(bytes)) > 0) {
            fileOutputStream.write(bytes, 0, length);
        }
        fileOutputStream.close();
        stream.close();
    }

    /**
     * 删除 minio 文件对象测试
     */
    @Test
    public void testRemove() {
        boolean result = MinioUtils.removeObject("test", "hdhd/待整理.md");
        Assert.assertTrue(result);
    }

    @Test
    public void getAllObjects(){
        List<String> test = MinioUtils.getAllObjects("logs");
        test.forEach(System.out::println);
    }

}