【Spring Cloud】新闻头条微服务项目:分布式文件系统MinIO实现文章页面存取

简介: 主要介绍了MinIO的功能特点以及为什么要用MinIO,并且还介绍了如何封装MinIO为工具快速进行使用

 一:概述

       MinIO基于Apache License v2.0开源协议的对象存储服务,可以做为云存储的解决方案用来保存海量的图片,视频,文档。由于采用Golang实现,服务端可以工作在Windows,Linux, OS X和FreeBSD上。配置简单,基本是复制可执行程序,单行命令可以运行起来。

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

二:为何选用MinIO

       其实我们也可以通过文章id到数据库中查询到该文章然后再通过模板引擎生成静态页面展示给用户,这种方式开发便捷,成本较低,但是将来要扩展业务的话比较困难。除此之外还可以采用第三方存储技术,比如阿里云、七牛云等,这种方案开发简单,功能强大且不用自己维护,缺点就是要收费。最后,还可以选用分布式文件系统来进行存储,其优点是容易实现扩容,但是复杂度较高,常用的分布式文件系统有FastDFS,其有如下优点:

    • 主从备份,高可用
    • 支持主从文件,支持自定义扩展名
    • 支持动态扩容

    其缺点就是没有完备的官方文档,近几年都没有进行过更新,而且环境搭建起来比较麻烦。

    而另外一个就是我们用到的MinIO,其优点如下

      • 性能高,准硬件条件下他能达到55GB/s的读,35GB/s的写速率
      • 部署自带管理页面
      • 它是MinIO.Inc运营的开源项目,社区活跃度高
      • 提供了所有主流开发语言的SDK

      当然其缺点就是不支持动态增加节点。

      三:MinIO特点

        • 数据保护
          Minio使用Minio Erasure Code(纠删码)来防止硬件故障。即便损坏一半以上的driver,但是仍然可以从中恢复。
        • 高性能
          作为高性能对象存储,在标准硬件条件下它能达到55GB/s的读、35GB/s的写速率
        • 可扩容
          不同MinIO集群可以组成联邦,并形成一个全局的命名空间,并跨越多个数据中心
        • SDK支持
          基于Minio轻量的特点,它得到类似Java、Python或Go等语言的sdk支持
        • 有操作页面
          面向用户友好的简单操作界面,非常方便的管理Bucket及里面的文件资源
        • 功能简单
          这一设计原则让MinIO不容易出错、更快启动
        • 丰富的API
          支持文件资源的分享连接及分享链接的过期策略、存储桶操作、文件列表访问及文件上传下载的基本功能等。
        • 文件变化主动通知
          存储桶(Bucket)如果发生改变,比如上传对象和删除对象,可以使用存储桶事件通知机制进行监控,并通过以下方式发布出去:AMQP、MQTT、Elasticsearch、Redis、NATS、MySQL、Kafka、Webhooks等。

        四:实现方案

        image.gif编辑

               首先根据文章内容通过模板引擎技术生成静态html文件之后,把生成的html文件访问路径存入到文章数据库中,并同时把文件存入分布式文件系统MinIO,当用户访问某一篇文章时候就会根据文章id查询到数据库中该文章的URL地址,然后再根据该地址从MinIO获取文件并返回。

        五:功能实现

        1.创建容器

        (1)拉取镜像

        docker pull minio/minio server

        image.gif

        (2)启动容器

        docker run -p 9000:9000 -p 9090:9090 --name minio -d --restart=always -e "MINIO_ROOT_USER=minio" -e "MINIO_ROOT_PASSWORD=minio123" -v /home/data:/data -v /home/config:/root/.minio minio/minio server /data --console-address ":9090"

        image.gif

        (3)管理控制台

        打开浏览器输入http://你的ip:9090/login即可进行登录,账号为minio,密码为minio123

        image.gif编辑

        (4)创建桶

        image.gif编辑

        点击Create Bucket创建一个名为headlines的桶

        2.封装MinIO为Start

        (1)模块创建

        image.gif编辑

        导入如下依赖:

        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-autoconfigure</artifactId>
            </dependency>
            <dependency>
                <groupId>io.minio</groupId>
                <artifactId>minio</artifactId>
                <version>7.1.0</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
        </dependencies>

        image.gif

        (2)配置类

        MinIOConfigProperties

        package com.my.file.config;
        import lombok.Data;
        import org.springframework.boot.context.properties.ConfigurationProperties;
        import java.io.Serializable;
        @Data
        @ConfigurationProperties(prefix = "minio")  // 文件上传 配置前缀file.oss
        public class MinIOConfigProperties implements Serializable {
            private String accessKey;
            private String secretKey;
            private String bucket;
            private String endpoint;
            private String readPath;
        }

        image.gif

        MinIOConfig

        package com.my.file.config;
        import com.my.file.service.FileStorageService;
        import io.minio.MinioClient;
        import lombok.Data;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
        import org.springframework.boot.context.properties.EnableConfigurationProperties;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        @Data
        @Configuration
        @EnableConfigurationProperties({MinIOConfigProperties.class})
        //当引入FileStorageService接口时
        @ConditionalOnClass(FileStorageService.class)
        public class MinIOConfig {
            @Autowired
            private MinIOConfigProperties minIOConfigProperties;
            @Bean
            public MinioClient buildMinioClient() {
                return MinioClient
                        .builder()
                        .credentials(minIOConfigProperties.getAccessKey(), minIOConfigProperties.getSecretKey())
                        .endpoint(minIOConfigProperties.getEndpoint())
                        .build();
            }
        }

        image.gif

        (3)封装操作MinIO类

        FileStorageService

        package com.my.file.service;
        import java.io.InputStream;
        /**
         * @author itheima
         */
        public interface FileStorageService {
            /**
             *  上传图片文件
             * @param prefix  文件前缀
             * @param filename  文件名
             * @param inputStream 文件流
             * @return  文件全路径
             */
            public String uploadImgFile(String prefix, String filename,InputStream inputStream);
            /**
             *  上传html文件
             * @param prefix  文件前缀
             * @param filename   文件名
             * @param inputStream  文件流
             * @return  文件全路径
             */
            public String uploadHtmlFile(String prefix, String filename,InputStream inputStream);
            /**
             * 删除文件
             * @param pathUrl  文件全路径
             */
            public void delete(String pathUrl);
            /**
             * 下载文件
             * @param pathUrl  文件全路径
             * @return
             *
             */
            public byte[]  downLoadFile(String pathUrl);
        }

        image.gif

        MinIOFileStorageService

        package com.my.file.service.impl;
        import com.my.file.config.MinIOConfig;
        import com.my.file.config.MinIOConfigProperties;
        import com.my.file.service.FileStorageService;
        import io.minio.GetObjectArgs;
        import io.minio.MinioClient;
        import io.minio.PutObjectArgs;
        import io.minio.RemoveObjectArgs;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.boot.context.properties.EnableConfigurationProperties;
        import org.springframework.context.annotation.Import;
        import org.springframework.stereotype.Service;
        import org.springframework.util.StringUtils;
        import java.io.ByteArrayOutputStream;
        import java.io.IOException;
        import java.io.InputStream;
        import java.text.SimpleDateFormat;
        import java.util.Date;
        @Slf4j
        @EnableConfigurationProperties(MinIOConfigProperties.class)
        @Import(MinIOConfig.class)
        @Service
        public class MinIOFileStorageService implements FileStorageService {
            @Autowired
            private MinioClient minioClient;
            @Autowired
            private MinIOConfigProperties minIOConfigProperties;
            private final static String separator = "/";
            /**
             * @param dirPath
             * @param filename  yyyy/mm/dd/file.jpg
             * @return
             */
            public String builderFilePath(String dirPath,String filename) {
                StringBuilder stringBuilder = new StringBuilder(50);
                if(!StringUtils.isEmpty(dirPath)){
                    stringBuilder.append(dirPath).append(separator);
                }
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
                String todayStr = sdf.format(new Date());
                stringBuilder.append(todayStr).append(separator);
                stringBuilder.append(filename);
                return stringBuilder.toString();
            }
            /**
             *  上传图片文件
             * @param prefix  文件前缀
             * @param filename  文件名
             * @param inputStream 文件流
             * @return  文件全路径
             */
            @Override
            public String uploadImgFile(String prefix, String filename,InputStream inputStream) {
                String filePath = builderFilePath(prefix, filename);
                try {
                    PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                            .object(filePath)
                            .contentType("image/jpg")
                            .bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1)
                            .build();
                    minioClient.putObject(putObjectArgs);
                    StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
                    urlPath.append(separator+minIOConfigProperties.getBucket());
                    urlPath.append(separator);
                    urlPath.append(filePath);
                    return urlPath.toString();
                }catch (Exception ex){
                    log.error("minio put file error.",ex);
                    throw new RuntimeException("上传文件失败");
                }
            }
            /**
             *  上传html文件
             * @param prefix  文件前缀
             * @param filename   文件名
             * @param inputStream  文件流
             * @return  文件全路径
             */
            @Override
            public String uploadHtmlFile(String prefix, String filename,InputStream inputStream) {
                String filePath = builderFilePath(prefix, filename);
                try {
                    PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                            .object(filePath)
                            .contentType("text/html")
                            .bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1)
                            .build();
                    minioClient.putObject(putObjectArgs);
                    StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
                    urlPath.append(separator+minIOConfigProperties.getBucket());
                    urlPath.append(separator);
                    urlPath.append(filePath);
                    return urlPath.toString();
                }catch (Exception ex){
                    log.error("minio put file error.",ex);
                    ex.printStackTrace();
                    throw new RuntimeException("上传文件失败");
                }
            }
            /**
             * 删除文件
             * @param pathUrl  文件全路径
             */
            @Override
            public void delete(String pathUrl) {
                String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");
                int index = key.indexOf(separator);
                String bucket = key.substring(0,index);
                String filePath = key.substring(index+1);
                // 删除Objects
                RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucket).object(filePath).build();
                try {
                    minioClient.removeObject(removeObjectArgs);
                } catch (Exception e) {
                    log.error("minio remove file error.  pathUrl:{}",pathUrl);
                    e.printStackTrace();
                }
            }
            /**
             * 下载文件
             * @param pathUrl  文件全路径
             * @return  文件流
             *
             */
            @Override
            public byte[] downLoadFile(String pathUrl)  {
                String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");
                int index = key.indexOf(separator);
                String bucket = key.substring(0,index);
                String filePath = key.substring(index+1);
                InputStream inputStream = null;
                try {
                    inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(minIOConfigProperties.getBucket()).object(filePath).build());
                } catch (Exception e) {
                    log.error("minio down file error.  pathUrl:{}",pathUrl);
                    e.printStackTrace();
                }
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                byte[] buff = new byte[100];
                int rc = 0;
                while (true) {
                    try {
                        if (!((rc = inputStream.read(buff, 0, 100)) > 0)) break;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    byteArrayOutputStream.write(buff, 0, rc);
                }
                return byteArrayOutputStream.toByteArray();
            }
        }

        image.gif

        (4)对外加入自动配置

        在resources中新建META-INF/spring.factories  

        org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
          com.my.file.service.impl.MinIOFileStorageService

        image.gif


        相关文章
        |
        5月前
        |
        人工智能 Java Nacos
        基于 Spring AI Alibaba + Nacos 的分布式 Multi-Agent 构建指南
        本文将针对 Spring AI Alibaba + Nacos 的分布式多智能体构建方案展开介绍,同时结合 Demo 说明快速开发方法与实际效果。
        4417 90
        |
        6月前
        |
        存储 安全 Java
        管理 Spring 微服务中的分布式会话
        在微服务架构中,管理分布式会话是确保用户体验一致性和系统可扩展性的关键挑战。本文探讨了在 Spring 框架下实现分布式会话管理的多种方法,包括集中式会话存储和客户端会话存储(如 Cookie),并分析了它们的优缺点。同时,文章还涵盖了与分布式会话相关的安全考虑,如数据加密、令牌验证、安全 Cookie 政策以及服务间身份验证。此外,文中强调了分布式会话在提升系统可扩展性、增强可用性、实现数据一致性及优化资源利用方面的显著优势。通过合理选择会话管理策略,结合 Spring 提供的强大工具,开发人员可以在保证系统鲁棒性的同时,提供无缝的用户体验。
        147 0
        |
        7月前
        |
        监控 Java API
        Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
        Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
        1196 3
        |
        5月前
        |
        负载均衡 Java API
        《深入理解Spring》Spring Cloud 构建分布式系统的微服务全家桶
        Spring Cloud为微服务架构提供一站式解决方案,涵盖服务注册、配置管理、负载均衡、熔断限流等核心功能,助力开发者构建高可用、易扩展的分布式系统,并持续向云原生演进。
        |
        8月前
        |
        Java 关系型数据库 数据库连接
        Spring Boot项目集成MyBatis Plus操作PostgreSQL全解析
        集成 Spring Boot、PostgreSQL 和 MyBatis Plus 的步骤与 MyBatis 类似,只不过在 MyBatis Plus 中提供了更多的便利功能,如自动生成 SQL、分页查询、Wrapper 查询等。
        850 3
        |
        8月前
        |
        Java 测试技术 Spring
        简单学Spring Boot | 博客项目的测试
        本内容介绍了基于Spring Boot的博客项目测试实践,重点在于通过测试驱动开发(TDD)优化服务层代码,提升代码质量和功能可靠性。案例详细展示了如何为PostService类编写测试用例、运行测试并根据反馈优化功能代码,包括两次优化过程。通过TDD流程,确保每项功能经过严格验证,增强代码可维护性与系统稳定性。
        331 0
        |
        8月前
        |
        存储 Java 数据库连接
        简单学Spring Boot | 博客项目的三层架构重构
        本案例通过采用三层架构(数据访问层、业务逻辑层、表现层)重构项目,解决了集中式开发导致的代码臃肿问题。各层职责清晰,结合依赖注入实现解耦,提升了系统的可维护性、可测试性和可扩展性,为后续接入真实数据库奠定基础。
        672 0
        |
        8月前
        |
        Java 应用服务中间件 Maven
        第01课:Spring Boot开发环境搭建和项目启动
        第01课:Spring Boot开发环境搭建和项目启动
        2314 0
        |
        7月前
        |
        存储 负载均衡 NoSQL
        【赵渝强老师】Redis Cluster分布式集群
        Redis Cluster是Redis的分布式存储解决方案,通过哈希槽(slot)实现数据分片,支持水平扩展,具备高可用性和负载均衡能力,适用于大规模数据场景。
        518 2
        |
        7月前
        |
        存储 缓存 NoSQL
        【📕分布式锁通关指南 12】源码剖析redisson如何利用Redis数据结构实现Semaphore和CountDownLatch
        本文解析 Redisson 如何通过 Redis 实现分布式信号量(RSemaphore)与倒数闩(RCountDownLatch),利用 Lua 脚本与原子操作保障分布式环境下的同步控制,帮助开发者更好地理解其原理与应用。
        464 6