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

本文涉及的产品
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
云原生网关 MSE Higress,422元/月
简介: 主要介绍了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


        相关文章
        |
        8天前
        |
        存储 Java 文件存储
        🗄️Spring Boot 3 整合 MinIO 实现分布式文件存储
        本文介绍了如何基于Spring Boot 3和MinIO实现分布式文件存储。随着应用规模扩大,传统的单机文件存储方案难以应对大规模数据和高并发访问,分布式文件存储系统成为更好的选择。文章详细讲解了MinIO的安装、配置及与Spring Boot的整合步骤,包括Docker部署、MinIO控制台操作、Spring Boot项目中的依赖引入、配置类编写及工具类封装等内容。最后通过一个上传头像的接口示例展示了具体的开发和测试过程,强调了将API操作封装成通用工具类以提高代码复用性和可维护性的重要性。
        97 7
        🗄️Spring Boot 3 整合 MinIO 实现分布式文件存储
        |
        2月前
        |
        Java 关系型数据库 数据库
        微服务SpringCloud分布式事务之Seata
        SpringCloud+SpringCloudAlibaba的Seata实现分布式事务,步骤超详细,附带视频教程
        137 1
        |
        3月前
        |
        存储 运维 数据可视化
        如何为微服务实现分布式日志记录
        如何为微服务实现分布式日志记录
        166 1
        |
        5月前
        |
        消息中间件 存储 负载均衡
        微服务与分布式系统设计看这篇就够了!
        【10月更文挑战第12天】 在现代软件架构中,微服务和分布式系统设计已经成为构建可扩展、灵活和可靠应用程序的主流方法。本文将深入探讨微服务架构的核心概念、设计原则和挑战,并提供一些关于如何在分布式系统中实现微服务的实用指导。
        225 2
        |
        5月前
        |
        人工智能 文字识别 Java
        SpringCloud+Python 混合微服务,如何打造AI分布式业务应用的技术底层?
        尼恩,一位拥有20年架构经验的老架构师,通过其深厚的架构功力,成功指导了一位9年经验的网易工程师转型为大模型架构师,薪资逆涨50%,年薪近80W。尼恩的指导不仅帮助这位工程师在一年内成为大模型架构师,还让他管理起了10人团队,产品成功应用于多家大中型企业。尼恩因此决定编写《LLM大模型学习圣经》系列,帮助更多人掌握大模型架构,实现职业跃迁。该系列包括《从0到1吃透Transformer技术底座》、《从0到1精通RAG架构》等,旨在系统化、体系化地讲解大模型技术,助力读者实现“offer直提”。此外,尼恩还分享了多个技术圣经,如《NIO圣经》、《Docker圣经》等,帮助读者深入理解核心技术。
        SpringCloud+Python 混合微服务,如何打造AI分布式业务应用的技术底层?
        |
        6月前
        |
        安全 应用服务中间件 API
        微服务分布式系统架构之zookeeper与dubbo-2
        微服务分布式系统架构之zookeeper与dubbo-2
        |
        6月前
        |
        负载均衡 Java 应用服务中间件
        微服务分布式系统架构之zookeeper与dubbor-1
        微服务分布式系统架构之zookeeper与dubbor-1
        |
        7月前
        |
        Java 数据库连接 微服务
        揭秘微服务架构下的数据魔方:Hibernate如何玩转分布式持久化,实现秒级响应的秘密武器?
        【8月更文挑战第31天】微服务架构通过将系统拆分成独立服务,提升了可维护性和扩展性,但也带来了数据一致性和事务管理等挑战。Hibernate 作为强大的 ORM 工具,在微服务中发挥关键作用,通过二级缓存和分布式事务支持,简化了对象关系映射,并提供了有效的持久化策略。其二级缓存机制减少数据库访问,提升性能;支持 JTA 保证跨服务事务一致性;乐观锁机制解决并发数据冲突。合理配置 Hibernate 可助力构建高效稳定的分布式系统。
        101 0
        |
        7月前
        |
        消息中间件 SQL 关系型数据库
        go-zero微服务实战系列(十、分布式事务如何实现)
        go-zero微服务实战系列(十、分布式事务如何实现)
        |
        7月前
        |
        监控 Go API
        带你十天轻松搞定 Go 微服务之大结局(分布式事务)
        带你十天轻松搞定 Go 微服务之大结局(分布式事务)