1.分布式文件系统应用场景

互联网海量非结构化数据的存储需求

  • 电商网站:海量商品图片
  • 视频网站:海量视频文件
  • 网盘 : 海量文件
  • 社交网站:海量图片

1.1 minio介绍

MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。

MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。

官网::https://min.io/
http://www.minio.org.cn/

1.2 优点

  1. 部署简单:一个single二进制文件即是一切,还可支持各种平台
  2. minio支持海量存储,可按zone扩展,支持单个对象最大5TB
  3. 兼容Amazon S3接口,充分考虑开发人员的需求和体验
  4. 低冗余且磁盘损坏高容忍,标准且最高的数据冗余系数为22(即存储一个1M的数据对象,实际占用磁盘空间为2M)。但在任意n/2块disk损坏的情况下依然可以读出数据(n为一个纠删码集合(Erasure Coding Set)中的disk数量)。并且这种损坏恢复是基于单个对象的,而不是基于整个存储卷的。

1.3 基础概念

object:存储到minio的基本对象,如文件、字节流、anything

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

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

set:即一组drive集合。分布式部署根据集群规模自动化分一个或多个set。每个set中的drive分布在不同位置。

  • 一个对象存储在一个set上
  • 一个集群划分为多个set
  • 一个set包含的drive数量是固定的,默认由系统根据集群规模自动计算得出
  • 一个set中的drive尽可能分布在不同的节点上

1.4 纠结码EC

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

1.5 存储形式

文件对象上传到 MinIO ,会在对应的数据存储磁盘中,以 Bucket 名称为目录,文件名称为下一级目录,文件名下是 part.1 和 xl.meta(老版本,最新版本如下图),前者是编码数据块及检验块,后者是元数据文件

image.png

2 minio环境搭建

官方文档:https://docs.min.io/docs/
中文文档:http://docs.minio.org.cn/docs/

2.1 单机部署

minio server 的standlone模式,即要管理的磁盘都在host本地。
用于实验环境和测试环境
在standlone模式中,分为non-erasure code mode和erasure code mode两种。

non-erasure code mode

在启动模式下,对于每一份对象数据,minio直接在data下=下面存储这份数据。不会建立副本,也不会启动纠删码机制。这种模式无论是服务实例还是磁盘都是“单点”。无任何高可用保障,磁盘损坏就表示数据丢失。

erasure code mode

minio server 实例传入多个本地磁盘参数。
一旦遇到多于一个磁盘参数,minio server 会自动启用 erasurecode mode。
erasure code对磁盘个数是有要求的,如不满足要求,实例启动将失败。
erasurecode mode启用后,要求传给minio server 的endpoint(单机模式下,即本地磁盘上的目录)至少为4个

基于centos7

  1. wget -q http://dl.minio.org.cn/server/minio/release/linux-amd64/minio
  2. chmod +x minio
  3. 启动
  4. ./minio server /mnt/data

默认用户名和密码:minioadmin

修改用户名
export MINIO_ROOT_USER=admin

修改密码
export MINIO_ROOT_PASSWORD=12345678

默认的配置目录是${HOME}/.minio

自定义配置目录
./minio server --config-dir /mnt/config /mnt/data
指定端口
./minio server --console-address ":50000" /mnt/data

基于docker

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

存在问题:
浏览器无法访问minio控制台,因为没有对外暴露控制台端口

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
自定义用户名和密码

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

minio纠删码模式

minio 使用纠删码erasure code 和校验和 checksum来保护数据免受硬件故障和无声数据损坏。即使丢失一半数量的硬盘,仍可以恢复数据

2.2 分布式部署

可以将多个硬盘组成一个对象存储服务,由于硬盘分布在不同的节点上,分布式minio避免了单点故障

分布式存储可靠性常用方法

分布式存储,关键点在于数据的可靠性,即保证数据的完整、不丢失、不损坏。
只有在可靠性实现的前提下,才有了追求一致性、高可用、高性能的基础。
对于存储领域,一般保证数据可靠性的方法有两类:冗余法校验法

冗余
对存储数据进行副本备份。
副本备份的多少,决定数据可靠性的高低
副本数据越多,数据越可靠,但需要的设备越多,成本越高。

校验
通过校验码对数学计算的方式,对出现丢失、损坏的数据进行校验、还原
①通过对数据进行校验和计算,检查数据是否完整,有无损坏或更改。在数据传输和保存是时经常用到
②恢复还原,通过对数据结合校验码,通过数学计算,还原丢失或损坏的数据。在保证数据可靠的前提下,降低冗余。

分布式minion优势

  1. 数据保护:minio采用纠删码来防范多个节点宕机和位衰减。minion至少需要4个硬盘,使用分布式minion自动引入了人纠删码功能
  2. 高可用:单机minio存在单点故障。相反,如果是一个有N块硬盘的分布式minion,只要有N/2硬盘在线,数据就是安全的,不过需要至少N/2+1个硬盘来创建新的对象
  3. 一致性:minio在分布式和单机模式下,所有读写操作都严格遵守read-after-write一致性原则

2.3 运行分布式minion

启动一个分布式Minio实例,你只需要把硬盘位置做为参数传给minio server命令即可,然后,你需要在
所有其它节点运行同样的命令。

  • 分布式Minio里所有的节点需要有同样的access秘钥和secret秘钥,这样这些节点才能建立联接。为了实现这个,你需要在执行minio server命令之前,先将access秘钥和secret秘钥export成环境变量。新版本使用MINIO_ROOT_USER&MINIO_ROOT_PASSWORD。
  • 分布式Minio使用的磁盘里必须是干净的,里面没有数据。
  • 下面示例里的IP仅供示例参考,你需要改成你真实用到的IP和文件夹路径。
  • 分布式Minio里的节点时间差不能超过3秒,你可以使用NTP 来保证时间一致。
  • 在Windows下运行分布式Minio处于实验阶段,请悠着点使用。

8个节点,每节点1块盘
启动分布式Minio实例,8个节点,每节点1块盘,需要在8个节点上都运行下面的命令:
[

](http://192.168.1.18/export8)

export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=12345678
minio server http://192.168.1.11/export1 http://192.168.1.12/export2 \
http://192.168.1.13/export3 http://192.168.1.14/export4 \
http://192.168.1.15/export5 http://192.168.1.16/export6 \
http://192.168.1.17/export7 http://192.168.1.18/export8

4个节点,每节点4块盘
启动分布式Minio实例,4个节点,每节点4块盘,需要在4个节点上都运行下面的命令:

export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=12345678
minio server http://192.168.1.11/export1 http://192.168.1.11/export2 \
http://192.168.1.11/export3 http://192.168.1.11/export4 \
http://192.168.1.12/export1 http://192.168.1.12/export2 \
http://192.168.1.12/export3 http://192.168.1.12/export4 \
http://192.168.1.13/export1 http://192.168.1.13/export2 \
http://192.168.1.13/export3 http://192.168.1.13/export4 \
http://192.168.1.14/export1 http://192.168.1.14/export2 \
http://192.168.1.14/export3 http://192.168.1.14/export4

2.4 使用Docker Compose部署MinIO

ttps://docs.min.io/docs/deploy-minio-on-docker-compose.html
要在Docker Compose上部署分布式MinIO,请下载docker-compose.yaml和nginx.conf到你当前的工作目录。

docker-compose pull
docker-compose up

2.5 扩展现有的分布式集群

通过区的方式启动minio集群

export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=12345678
minio server http://host{1...32}/export{1...32}

制定新的集群扩展现有集群(纠删码)

export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=12345678
minio server http://host{1...32}/export{1...32} http://host{33...64}/export{1...32}

现在整个集群就扩展了1024个磁盘,总磁盘变为2048个,新的对象上传请求会自动分配到最少使用的集群上。通过以上扩展策略,您就可以按需扩展您的集群。重新配置后重启集群,会立即在集群中生效,并对现有集群无影响。如上命令中,我们可以把原来的集群看做一个区,新增集群看做另一个区,新对象按每个区域中的可用空间比例放置在区域中。在每个区域内,基于确定性哈希算法确定位置

2.6 基于nginx实现loadbalancer

upstream minio {
    server 192.168.3.14:9001;
    server 192.168.3.14:9002;
    server 192.168.3.14:9003;
    server 192.168.3.14:9004;
}
upstream console {
    ip_hash;
    server 192.168.3.14:50001;
    server 192.168.3.14:50002;
    server 192.168.3.14:50003;
    server 192.168.3.14:50004;
}
server {
    listen 9000;
    listen [::]:9000;
    server_name localhost;
    # To allow special characters in headers
    ignore_invalid_headers off;
    # Allow any size file to be uploaded.
    # Set to a value such as 1000m; to restrict file size to a specific
    value
    client_max_body_size 0;
    # To disable buffering
    proxy_buffering off;
    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_connect_timeout 300;
        # Default is HTTP/1, keepalive is only enabled in HTTP/1.1
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        chunked_transfer_encoding off;
        proxy_pass http://minio;
    }
}
server {
    listen 50000;
    listen [::]:50000;
    server_name localhost;
    # To allow special characters in headers
    ignore_invalid_headers off;
    # Allow any size file to be uploaded.
    # Set to a value such as 1000m; to restrict file size to a specific
    value
    client_max_body_size 0;
    # To disable buffering
    proxy_buffering off;
    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-NginX-Proxy true;
        proxy_connect_timeout 300;
        # Default is HTTP/1, keepalive is only enabled in HTTP/1.1
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        chunked_transfer_encoding off;
        proxy_pass http://console;
    }
}

3 minion 客户端使用

http://docs.minio.org.cn/docs/master/minio-client-complete-guide#ls

ls 列出文件和文件夹。
mb 创建一个存储桶或一个文件夹。
cat 显示文件和对象内容。
pipe 将一个STDIN重定向到一个对象或者文件或者STDOUT。
share 生成用于共享的URL。
cp 拷贝文件和对象。
mirror 给存储桶和文件夹做镜像。
find 基于参数查找文件。
diff 对两个文件夹或者存储桶比较差异。
rm 删除文件和对象。
events 管理对象通知。
watch 监视文件和对象的事件。
policy 管理访问策略。
config 管理mc配置文件。
update 检查软件更新。
version 输出版本信息

部署

wget http://dl.minio.org.cn/client/mc/release/linux-amd64/mc
wget http://dl.minio.org.cn/client/mc/release/windows-amd64/mc.exe

chmod +x mc

./mc --help

mv mc /usr/local/sbin/

配置

# 查询mc host配置
mc config host ls

# 添加minio服务
mc config host add minio-server http://192.168.3.14:9000 admin 12345678

# 删除host
mc config host remove minio-server

MC命令使用

上传下载

# 查询minio服务上的所有buckets(文件和文件夹)
mc ls minio-server

# 下载文件
mc cp minio-server/tulingmall/fox/fox.jpg /tmp/

#删除文件
mc rm minio-server/tulingmall/fox/fox.jpg

#上传文件
mc cp zookeeper.out minio-server/tulingmall/

bucket 管理

# 创建bucket
mc mb minio-server/bucket01

# 删除bucket
mc rb minio-server/bucket02

# bucket不为空,可以强制删除 慎用
mc rb --force minio-server/bucket01

#查询bucket03磁盘使用情况
mc du minio-server/bucket03

MC admin命令使用

service 服务重启并停止所有MinIO服务器
update 更新更新所有MinIO服务器
info 信息显示MinIO服务器信息
user 用户管理用户
group 小组管理小组
policy MinIO服务器中定义的策略管理策略
config 配置管理MinIO服务器配置
heal 修复MinIO服务器上的磁盘,存储桶和对象
profile 概要文件生成概要文件数据以进行调试
top 顶部提供MinIO的顶部统计信息
trace 跟踪显示MinIO服务器的http跟踪
console 控制台显示MinIO服务器的控制台日志
prometheus Prometheus管理Prometheus配置
kms kms执行KMS管理操作

用户管理

mc admin user --help
#新建用户
mc admin user add minio-server fox
mc admin user add minio-server fox02 12345678
#查看用户
mc admin user list minio-server
#禁用用户
mc admin user disable minio-server fox02
#启用用户
mc admin user disable minio-server fox02
#查看用户信息
mc admin user info minio-server fox
#删除用户
mc admin user remove minio-server fox02

策略管理

policy命令,用于添加,删除,列出策略,获取有关策略的信息并为MinIO服务器上的用户设置策略

mc admin policy --help
#列出MinIO上的所有固定策略
mc admin policy list minio-server
# 查看plicy信息
mc admin policy info minio-server readwrite
# 添加新的策略
mc admin policy add minio-server tulingmall-admin /root/tulingmall.json
mc admin policy list minio-server

#新建用户
mc admin user add minio-server fox03 12345678
# 设置用户的访问策略
mc admin policy set minio-server tulingmall-admin user=fox03

4 minio java client 使用

官方demo: https://github.com/minio/minio-java
官方文档:https://docs.min.io/docs/java-client-api-reference.html

<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>

yml

minio:
    end_point: http://localhost:9000
    access_key: minioadmin
    secret_key: minioadmin
    bucket_name: basebucket

MinioConfig

package com.alibaba.xiaomi.commonmanager.controller.config;

import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;

/**
 * @Author gett
 * @Date 2021/12/8  17:33
 * @Description
 */

public class MinioConfig {

    @Value("${common-base.minio.end_point}")
    private String endPoint;
    @Value("${common-base.minio.access_key}")
    private String accessKey;
    @Value("${common-base.minio.secret_key}")
    private String secretKey;

    @Bean
    public MinioClient minioClient(){
        MinioClient build = MinioClient.builder().endpoint(endPoint).credentials(accessKey,secretKey).build();
        return build;
    }
}
package com.alibaba.xiaomi.commonmanager.controller;

import brave.Tracer;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.xiaomi.commonbase.bean.RspBean;
import com.alibaba.xiaomi.commonbase.enums.RspCodeEnum;
import com.alibaba.xiaomi.commonbase.idutil.IdUtil;
import com.alibaba.xiaomi.commonmanager.controller.checkItem.CheckItemController;
import com.alibaba.xiaomi.commonmanager.controller.config.MinioConfigs;
import io.minio.*;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * @Author gett
 * @Date 2021/12/8  16:42
 * @Description 上传和下载
 */

@RestController
public class Demo {

    private static Logger logger = LoggerFactory.getLogger(Demo.class);

    @Value("${minio.bucket_name}")
    private String bucketName;


    @Value("${minio.end_point}")
    private  String endPoint;

    @Autowired
    private MinioClient minioClient;

    @Autowired
    Tracer tracer;


    /**
     * 上传文件
     * @param media
     * @return
     * @throws Exception
     */

    @RequestMapping("/upload")
    public RspBean upload(@RequestParam(value="media") MultipartFile[] media) throws  Exception{

//        判断文件是否为空
        if (media==null || media.length==0){
            return new RspBean(RspCodeEnum.FAIL,"上传文件不能为空",tracer.currentSpan().context().traceIdString(),"fail");
        }

//        判断桶是否存在,不存在则创建
        if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())){
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }



        List<Object> objects = new ArrayList<>(media.length);

        for (MultipartFile file:media){
            String originalFilename = file.getOriginalFilename();
            // 新的文件名 = 存储桶名称_时间戳.后缀名
            String fileName = IdUtil.getId() + originalFilename.substring(originalFilename.lastIndexOf("."));

            objects.add(fileName);
            try{
                InputStream inputStream = file.getInputStream();

                minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(
                        inputStream, file.getSize(), -1
                ).contentType(file.getContentType()).build());

                inputStream.close();


                JSONObject resultJson = new JSONObject();
                resultJson.put("originalName",file.getOriginalFilename());
                resultJson.put("bucketName", bucketName);
                resultJson.put("fileName", fileName);
                resultJson.put("url",endPoint+"/"+bucketName+"/"+fileName);



                return new RspBean(RspCodeEnum.SUCCESS,"",tracer.currentSpan().context().traceIdString(),resultJson);

            }catch (Exception e){
                logger.info("上传接口异常:{}",e.getMessage());
                return new RspBean(RspCodeEnum.ERROR,"上传接口异常",tracer.currentSpan().context().traceIdString(),"fail");

            }
        }

        return new RspBean(RspCodeEnum.FAIL,"",tracer.currentSpan().context().traceIdString(),"fail");

    }


    @RequestMapping("/download")
    public RspBean download(String fileName) throws  Exception{
        try {

            boolean flag = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());

            if (flag){
                //              获取对象信息
                StatObjectResponse response = minioClient.statObject(
                        StatObjectArgs.builder().bucket(bucketName).object(fileName).build()
                );
                if (response != null) {
                    minioClient.downloadObject(DownloadObjectArgs.builder()
                            .bucket(bucketName)
                            .object(fileName)
                            .filename(fileName)
                            .build());
                    return new RspBean(RspCodeEnum.SUCCESS, "下载接口成功", tracer.currentSpan().context().traceIdString(),
                            "success");

                }
            }else {
                return new RspBean(RspCodeEnum.ERROR,"桶不存在",tracer.currentSpan().context().traceIdString(),"fail");
            }

        }catch (Exception e){
            logger.info("下载接口异常:{}",e.getMessage());
            return new RspBean(RspCodeEnum.ERROR,"下载接口异常",tracer.currentSpan().context().traceIdString(),"fail");
        }
        return new RspBean(RspCodeEnum.ERROR,"下载接口异常",tracer.currentSpan().context().traceIdString(),"FAIL");


    }

}