5、分布式存储Minio

分布式存储

1、分布式存储是一种数据存储技术,通过网络使用企业中的每台机器上的磁盘空间,并将这些分散的存储资源构成一个虚拟的存储设备,数据分散的存储在企业的各个角落。

2、分布式存储系统,是将数据分散存储在多台独立的设备上。传统的网络存储系统采用集中的存储服务器存放所有数据,存储服务器成为系统性能的瓶颈,也是可靠性和安全性的焦点,不能满足大规模存储应用的需要。分布式网络存储系统采用可扩展的系统结构,利用多台存储服务器分担存储负荷,利用位置服务器定位存储信息,它不但提高了系统的可靠性、可用性和存取效率,还易于扩展。

MinIO Quickstart Guide

MinIO 是在 GNU Affero 通用公共许可证 v3.0 下发布的高性能对象存储。 它是与 Amazon S3 云存储服务兼容的 API。 使用 MinIO 为机器学习、分析和应用程序数据工作负载构建高性能基础架构。

对于 Kubernetes 环境,请使用 MinIO Kubernetes Operator

对象存储实现

一、系统搭建

1、Linux

使用以下命令在运行 64 位 Intel/AMD 架构的 Linux 主机上运行独立的 MinIO 服务器。将/data 替换为您希望 MinIO 存储数据的驱动器或目录的路径。

wget http://dl.minio.org.cn/server/minio/release/linux-amd64/minio
chmod +x minio
./minio server /data

/data 替换为您希望 MinIO 存储数据的驱动器或目录的路径。

2、window

要在 64 位 Windows 主机上运行 MinIO,请从以下 URL 下载 MinIO 可执行文件:

http://dl.minio.org.cn/server/minio/release/windows-amd64/minio.exe

使用以下命令在 Windows 主机上运行独立的 MinIO 服务器。 将“D:\”替换为您希望 MinIO 存储数据的驱动器或目录的路径。 您必须将终端或 powershell 目录更改为 minio.exe 可执行文件的位置,将该目录的路径添加到系统 $PATH

minio.exe server D:\

image-20210826145340388

MinIO 部署开始使用默认的 root 凭据 minioadmin:minioadmin。您可以使用 MinIO 控制台测试部署,这是一个内置在 MinIO 服务器中的基于 Web 的嵌入式对象浏览器。将主机上运行的 Web 浏览器指向 http://127.0.0.1:9000 并使用 root 凭据登录。您可以使用浏览器来创建桶、上传对象以及浏览 MinIO 服务器的内容。

image-20210826145904734

二、java开发

中文参考API:http://docs.minio.org.cn/docs/

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.2.1</version>
</dependency>

需要有存储服务的三个参数才能连接到该服务

参数 说明
Endpoint 对象存储服务的URL
Access Key Access key就像用户ID,可以唯一标识你的账户。
Secret Key Secret key是你账户的密码。

MinIO和springboot整合

1、配置文件
#minio配置
minio:
  url: http://localhost:9000/
  username: minioadmin
  password: minioadmin
2、工具类
@Component
@Slf4j
public class MinioUtil {

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

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

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

    private MinioClient minioClient;

    @Bean
    private void init(){
        minioClient = MinioClient.builder().endpoint(url).credentials(username,password).build();
        log.info("###### 初始化minio客户端完成,server:" + url + " ######");
    }

    /**
     * 查看桶是否已经存在
     * @param bucketName
     * @return
     */
    public boolean bucketExist(String bucketName){
        try {
            BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder().bucket(bucketName).build();
            return minioClient.bucketExists(bucketExistsArgs);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 创建桶
     * @param bucketName
     */
    public void createBucket(String bucketName){
        try {
            MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder().bucket(bucketName).build();
            minioClient.makeBucket(makeBucketArgs);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 上传文件,使用上传文件的文件名
     * @param multipartFile
     * @param bucketName
     * @param path 文件在桶中的路径,开头不要加/,末尾加/
     */
    public String uploadFile(MultipartFile multipartFile, String bucketName, String path){
        try {
            String objectFile = path + multipartFile.getOriginalFilename();
            PutObjectArgs putObjectArgs = PutObjectArgs
                .builder()
                .bucket(bucketName)
                .stream(multipartFile.getInputStream(), multipartFile.getSize(), -1)
                .object(objectFile)
                .contentType(multipartFile.getContentType())
                .build();
            minioClient.putObject(putObjectArgs);
            return "/" + bucketName + "/" + objectFile;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 上传文件,使用自定义的文件名
     * @param multipartFile
     * @param bucketName
     * @param path 文件在桶中的路径,开头不要加/,末尾加/
     * @param fileName 自定义文件名,不用写后缀
     */
    public String uploadFile(MultipartFile multipartFile, String bucketName, String path ,String fileName){
        try {
            String originalFilename = multipartFile.getOriginalFilename();
            String fileTyle = originalFilename.substring(originalFilename.lastIndexOf("."),originalFilename.length());
            String objectFile = path + fileName + fileTyle;
            PutObjectArgs putObjectArgs = PutObjectArgs
                .builder()
                .bucket(bucketName)
                .stream(multipartFile.getInputStream(), multipartFile.getSize(), -1)
                .object(objectFile)
                .contentType(multipartFile.getContentType())
                .build();
            minioClient.putObject(putObjectArgs);
            return "/" + bucketName + "/" + objectFile;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取上传文件的访问url
     * @param bucketName
     * @param fileName
     * @return
     */
    public String getFileUrl(String bucketName, String fileName) {

        try {
            GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.
                builder()
                .method(Method.GET)
                .bucket(bucketName)
                .object(fileName)
                .build();
            minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs);
        }catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 删除文件
     * @param buketName
     * @param objectName 此处为文件在桶中的全路径名,开头不要加/
     */
    public void removeFile(String buketName, String objectName){
        try {
            RemoveObjectArgs removeObjectArgs = RemoveObjectArgs
                .builder()
                .bucket(buketName)
                .object(objectName)
                .build();
            minioClient.removeObject(removeObjectArgs);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

InputStream转MutipartFile

由于我们工具类使用的是MultipartFile进行文件上传,所以需要有inputStreamMultipartFile

public class MultipartFileUtil {

    /**
     * inputStream转MultipartFile
     * @param inputStream
     * @param fileName
     * @return
     */
    public static MultipartFile getMultipartFile(InputStream inputStream, String fileName) {
        FileItem fileItem = createFileItem(inputStream, fileName);
        //CommonsMultipartFile是feign对multipartFile的封装,但是要FileItem类对象
        return new CommonsMultipartFile(fileItem);
    }


    /**
     * FileItem类对象创建
     * @param inputStream
     * @param fileName
     * @return FileItem
     */
    private static FileItem createFileItem(InputStream inputStream, String fileName) {
        FileItemFactory factory = new DiskFileItemFactory(16, null);
        String textFieldName = "file";
        FileItem item = factory.createItem(textFieldName, MediaType.MULTIPART_FORM_DATA_VALUE, true, fileName);
        int bytesRead = 0;
        byte[] buffer = new byte[8192];
        OutputStream os = null;
        //使用输出流输出输入流的字节
        try {
            os = item.getOutputStream();
            while ((bytesRead = inputStream.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            inputStream.close();
        } catch (Exception e) {
            throw new IllegalArgumentException("文件上传失败");
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        return item;
    }
}