MinIO - 基本使用

11/2/2022 DFS,minio

# 第一章 简介

官方文档:https://docs.min.io/docs/

中文文档:http://docs.minio.org.cn/docs/ (没有及时更新,容易被坑)

MinIO 是一种高性能对象存储解决方案,它提供与 Amazon Web Services S3 兼容的 API 并支持所有核心 S3 功能。

MinIO 旨在部署在任何地方——公共或私有云、裸机基础设施、编排环境和边缘基础设施。

该站点记录了 Linux 平台上 MinIO 部署的操作、管理和开发。

本文档针对 MinIO 的最新稳定版本:RELEASE.2022-10-29T06-21-33Z (opens new window)

MinIO 在双重许可证GNU Affero 通用公共许可证 v3.0 (opens new window)MinIO 商业许可证下发布 (opens new window)

可以使用的服务器 https://play.min.io (opens new window)开始探索 MinIO 功能。play是一个运行最新稳定 MinIO 服务器的公共MinIO 集群。上传到的任何文件play都应被视为公开且不受保护。

Minio优点

  • 部署简单: 一个single二进制文件即是一切,还可支持各种平台。

  • minio支持海量存储,可按zone扩展(原zone不受任何影响),支持单个对象最大5TB;

  • 兼容Amazon S3接口,充分考虑开发人员的需求和体验;

  • 图灵学院低冗余且磁盘损坏高容忍,标准且最高的数据冗余系数为2(即存储一个1M的数据对象,实际占用

  • 磁盘空间为2M)。但在任意n/2块disk损坏的情况下依然可以读出数据(n为一个纠删码集合(Erasure

  • Coding Set)中的disk数量)。并且这种损坏恢复是基于单个对象的,而不是基于整个存储卷的。

  • 读写性能优异

MinIO的基础概念

  • Object:存储到 Minio 的基本对象,如文件、字节流,Anything...

    Bucket:用来存储 Object 的逻辑空间。每个 Bucket 之间的数据是相互隔离的。对于客户端而言,就相当于一个存放文件的顶层文件夹。

    Drive:即存储数据的磁盘,在 MinIO 启动时,以参数的方式传入。Minio 中所有的对象数据都会存储在 Drive 里。

    Set :即一组 Drive 的集合,分布式部署根据集群规模自动划分一个或多个 Set ,每个 Set 中的Drive 分布在不同位置。一个对象存储在一个 Set 上。(For example: {1...64} is divided into 4 sets each of size 16.)

    • 一个对象存储在一个Set上

    • 一个集群划分为多个Set

    • 一个Set包含的Drive数量是固定的,默认由系统根据集群规模自动计算得出

    • 一个SET中的Drive尽可能分布在不同的节点上

# 第二章 安装

# 2.1 单机 - 二进制文件安装

  1. 使用以下命令下载最新的稳定 MinIO 二进制文件并将其安装到系统中$PATH

    wget https://dl.min.io/server/minio/release/linux-amd64/minio
    chmod +x minio
    sudo mv minio /usr/local/bin/
    
    1
    2
    3
  2. 启动 MinIO 服务器

    mkdir /opt/minio
    mkdir /opt/minio/data
    mkdir /opt/minio/log
    minio server /opt/minio/data --console-address :9090
    nohup minio server --console-address :9090 /opt/minio/data > /opt/minio/log/minio.log 2>&1 &
    
    1
    2
    3
    4
    5
  3. 默认用户名密码minioadmin:minioadmin,修改默认用户名密码可以使用:

    export MINIO_ROOT_USER=admin
    export MINIO_ROOT_PASSWORD=damoncai
    
    1
    2
  4. 默认的配置目录是${HOME}/.minio,可以通过--config-dir命令自定义配置目录:

    ./minio server --config-dir /mnt/config /mnt/data
    
    1
  5. 控制台监听端口是动态生成的,可以通过--console-address ":port"指定静态端口

    ./minio server --console-address ":50000" /mnt/data
    
    1
  6. 访问

    IP:50000
    
    1

# 2.2 单机 - Docker 启动

docker run -p 9000:9000 --name minio \
-v /mnt/data:/data \
-v /mnt/config:/root/.minio \
minio/minio server /data
1
2
3
4

对外暴露minio控制台的端口,通过--console-address ":50000"指定控制台端口为静态端口

docker run -p 9000:9000 -p 50000:50000 --name minio \
-v /mnt/data:/data \
-v /mnt/config:/root/.minio \
minio/minio server --console-address ":50000" /data
1
2
3
4

MinIO自定义用户名密码

docker run -d -p 9000:9000 -p 50000:50000 --name minio \
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=12345678" \
-v /mnt/data:/data \
-v /mnt/config:/root/.minio \
minio/minio server --console-address ":50000" /data
1
2
3
4
5
6

# 2.3 单机 - 纠删码模式

docker run -d -p 9000:9000 -p 50000:50000 --name minio \
-v /mnt/data1:/data1 \
-v /mnt/data2:/data2 \
-v /mnt/data3:/data3 \
-v /mnt/data4:/data4 \
-v /mnt/data5:/data5 \
-v /mnt/data6:/data6 \
-v /mnt/data7:/data7 \
-v /mnt/data8:/data8 \
minio/minio server /data{1...8} --console-address ":50000"
1
2
3
4
5
6
7
8
9
10

# 2.4 分布式集群部署

TODO

# 第三章 Java SDK

**中文文档:**https://github.com/minio/minio-java/blob/master/docs/zh_CN/API.md

创建工程

SpringBoot-MinIO

依赖

<dependencies>
    <dependency>
        <groupId>io.minio</groupId>
        <artifactId>minio</artifactId>
        <version>8.3.0</version>
    </dependency>
    <dependency>
        <groupId>me.tongfei</groupId>
        <artifactId>progressbar</artifactId>
        <version>0.5.3</version>
    </dependency>
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>4.8.1</version>
    </dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

相关操作

package top.damoncai.minio;


import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import org.junit.Before;
import org.junit.Test;

import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;

public class Demo {

    MinioClient minioClient;



    @Before
    public void before() {
        minioClient = MinioClient.builder()
                .endpoint("http://xxxxx:9000")
                .credentials("xxxx", "xxxx")
                .build();
    }

    //******************************** 存储桶操作 - Start ********************************
    /**
     * 判断bucket是否存在
     */
    @Test
    public void existBucket() throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        String bucketName = "test";
        BucketExistsArgs bucket = BucketExistsArgs.builder().bucket(bucketName).build();
        boolean exist = minioClient.bucketExists(bucket);
        System.out.println(bucketName + ":" + exist);
    }

    /**
     * 创建bucket
     */
    @Test
    public void createBucket() throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        String bucketName = "test";
        BucketExistsArgs bucket = BucketExistsArgs.builder().bucket(bucketName).build();
        boolean exist = minioClient.bucketExists(bucket);
        if(!exist)
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * bucket 列表
     */
    @Test
    public void bucketList() throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        List<Bucket> buckets = minioClient.listBuckets();
        for (Bucket bucket : buckets) {
            System.out.println(bucket.name() + " - " + bucket.creationDate());
        }
    }


    /**
     * 删除bucket
     * 注意,只有存储桶为空时才能删除成功。
     */
    @Test
    public void removeBucket() throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        String bucketName = "test";
        RemoveBucketArgs removeBucketArgs = RemoveBucketArgs.builder().bucket(bucketName).build();
        minioClient.removeBucket(removeBucketArgs);
    }

    /**
     * 列出某个存储桶中的所有对象。
     *
     * bucketName: 存储桶名称。
     * prefix: 对象名称的前缀,列出有该前缀的对象
     * recursive: 是否递归查找,如果是false,就模拟文件夹结构查找
     */
    @Test
    public void listObjects() throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        String bucketName = "test";
        ListObjectsArgs listObjects = ListObjectsArgs.builder().bucket(bucketName).recursive(true).build();
        Iterable<Result<Item>> objs = minioClient.listObjects(listObjects);

        for (Result<Item> obj : objs) {
            Item item = obj.get();
            System.out.println(item.objectName() + " - " +item.size());
        }
    }


    /**
     * 获得指定对象前缀的存储桶策略
     */
    @Test
    public void getBucketPolicy() throws Exception {
        String bucketName = "test";
        GetBucketPolicyArgs getBucketPolicy = GetBucketPolicyArgs.builder().bucket(bucketName).build();
        String bucketPolicy = minioClient.getBucketPolicy(getBucketPolicy);
        System.out.println(bucketPolicy);
    }


    /**
     * 占位符
     */
    private static final String BUCKET_PARAM = "${bucket}";

    /**
     * bucket权限-读写
     */
    private static final String READ_WRITE = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"],\"Resource\":[\"arn:aws:s3:::" + "test" + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\"],\"Resource\":[\"arn:aws:s3:::" + "test" + "/*\"]}]}";


    /**
     * bucket权限-只读
     */
    private static final String WRITE_ONLY = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucketMultipartUploads\"],\"Resource\":[\"arn:aws:s3:::" + "test" + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\"],\"Resource\":[\"arn:aws:s3:::" + "test" + "/*\"]}]}";


    /**
     * 设置指定对象前缀的存储桶策略
     */
    @Test
    public void setBucketPolicy() throws Exception {
        String bucketName = "test";
        SetBucketPolicyArgs setBucketPolicy = SetBucketPolicyArgs.builder().bucket(bucketName).config(WRITE_ONLY).build();
        this.minioClient.setBucketPolicy(setBucketPolicy);
    }


    /**
     * 以流的形式下载一个对象
     */
    @Test
    public void getObject() throws Exception {
        String bucketName = "test";
        GetObjectArgs getObject = GetObjectArgs.builder().bucket(bucketName).object("a.jpg").build();
        GetObjectResponse gor = this.minioClient.getObject(getObject);
        byte[] b = new byte[1024];
        gor.read(b);
        FileOutputStream fos = new FileOutputStream("D:\\aaa.jpg");
        fos.write(b);
    }


    /**
     * 下载文件
     */
    @Test
    public void download() throws Exception {
        String bucketName = "test";
        DownloadObjectArgs build = DownloadObjectArgs.builder().bucket(bucketName).object("a.jpg").filename("D:\\kkk.jpg").build();
        minioClient.downloadObject(build);
    }

    /**
     * 通过InputStream上传对象。
     */
    @Test
    public void putObj() throws Exception {
        String bucketName = "test";
        File file = new File("D:\\kkk.jpg");
        FileInputStream fis = new FileInputStream(file);
        PutObjectArgs put = PutObjectArgs.builder().bucket(bucketName).object("hello.png").stream(fis, file.length(), -1).build();
        minioClient.putObject(put);
    }


    /**
     * 通过文件上传对象。
     */
    @Test
    public void upload() throws Exception {
        UploadObjectArgs uploadObjectArgs = UploadObjectArgs
                .builder()
                .bucket("test")
                .object("hello/a.jpg")
                .filename("d:\\2.png")
                .build();
        minioClient.uploadObject(uploadObjectArgs);
    }

    /**
     * Presigned操作
     * expiry: 失效时间(以秒为单位),默认是7天,不得大于七天
     */
    @Test
    public void presigned() throws Exception {
        GetPresignedObjectUrlArgs test = GetPresignedObjectUrlArgs.builder().bucket("test").object("a.jpg").method(Method.GET).build();
        String presignedObjectUrl = minioClient.getPresignedObjectUrl(test);
        System.out.println(presignedObjectUrl);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201

# 第四章 自定义minio-starter

Last Updated: 11/4/2022, 6:20:44 PM