Java项目中集成Minio,构建高效分布式对象存储解决方案

Java项目中集成Minio,构建高效分布式对象存储解决方案

如果您觉得这篇文章有帮助的话!给个点赞和评论支持下吧,感谢~

作者:哇子

IT技术(摸鱼)交流QQ群:374984174

Minio介绍

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

也许提起对象存储技术,我们都经历过Fastdfs长时间的拉锯战,即使现在大部分都在使用云服务厂商提供的OSS对象存储服务,但是其所花费的经济成本也是递增的,而且数据都存储在别人的服务器上,从一定程度上来说,对于文件资源的把控粒度是极其不可控制,公网数据是何其的没有隐私可言。虽然,对于开发层面上来说,只需要整合对应的SDK,对其使用已经是开箱即用。但是,对于选择自研对象存储技术来说,Minio何尝不失为一大利器。
其搭建过程与整合方面,几乎已经没有什么瓶颈可言。不论是从传统服务器的安装,还是基于Docker以及Kubernetes的部署,简直简单得不要不要的。及其优势也是很多的:

  1. 海量存储:Minio支持大规模数据存储,可以轻松应对PB级别的数据量。
  2. 弹性扩展:Minio的容量和处理能力可以弹性扩展,满足不同业务的需求。
  3. 多存储类型:Minio支持块存储、文件存储和对象存储等多种存储类型,可根据实际需求选择。
  4. 高可用性:Minio具备高可用性特点,能够保证数据的安全性和可靠性。
  5. 低成本:Minio全面优化了存储成本,为企业节省了大量的存储费用。

官网文档地址:https://docs.min.io/cn/

Minio支持独立部署和分布式部署,我们先看看单机部署,后面再讲分布式部署

  • 独立部署:具有单个存储卷或文件夹的单个 MinIO 服务器。独立部署最适合使用 MinIO 进行对象存储的应用程序的评估和初始开发,或为单个存储卷提供 S3 访问层。独立部署不提供对全套 MinIO 高级 S3 特性和功能的访问。
  • 分布式部署:一台或多台 MinIO 服务器,所有服务器上至少有四个总存储卷。分布式部署最适合生产环境和工作负载,并支持 MinIO 的所有核心和高级 S3 特性和功能。对于生产环境,MinIO 建议使用 4 个节点和 4 个驱动器的基线拓扑。

Linux安装Minio

1、准备安装文件

cd  /home/minio
#在线下载二进制文件
wget https://dl.min.io/server/minio/release/linux-amd64/minio

2、安装

2.1 赋权
#提权
chmod +x minio
2.2 设置临时用户名、密码(重启后会失效)
#旧版使用 MINIO_ACCESS_KEY MINIO_SECRET_KEY,作废时间:Deprecated since version RELEASE.2021-04-22T15-44-28Z.
#新版 配置用户名密码
export MINIO_ROOT_USER=minioadmin
export MINIO_ROOT_PASSWORD=yourpassword

#查看环境变量
echo $MINIO_ROOT_USER
echo $MINIO_ROOT_PASSWORD
2.3 设置永久环境变量
# 修改系统配置
vim /etc/profile

#最后一行输入(新版) 
export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=password

# 设置立即生效
source /etc/profile
2.4 创建存储目录及日志文件
#创建存储目录
mkdir -p  /home/minio/data
#进入
cd /home/minio
#创建日志文件
touch minio.log

3、后台启动

nohup /home/minio/minio server --address :9800 --console-address :9889 /home/minio/data >/home/minio/minio.log 2>&1 &
备注
   nohup:后台启动
   ./minio server:启动命令
   --address :9800:指定API端口
   --console-address :9889:指定控制台端口
  /home/minio/data:指定存储目录
  >/home/minio/minio.log 2>&1 :控制台日志重定向到/home/minio/minio.log文件中
  &:后台运行

4、网页登录

地址:主机IP:9889

密码为之前设置的admin账号

此处新建的管理员用户用于Nacos配置文件中配置Minio的账户,不能使用默认管理员账号的原因有以下两点,一是不安全,二是Minio如果宕机重启后默认的用户名和密码会变成minioadmin

,所以需要手动创建一个管理员用户。

5、防止服务宕机可用性,设置Minio开机自启

 cd /etc/rc.d/init.d
 #新建shell脚本文件
 vi startMinio.sh

写入以下内容:

nohup /home/minio/minio server --address :9800 --console-address :9889 /home/minio/data >/home/minio/minio.log 2>&1 &

配置开机自启:

#给shell脚本赋权
chmod +x startMinio.sh
#添加到开机自启动服务中
chkconfig --add startMinio.sh
#设置开机自启动
chkconfig startMinio.sh on
#查看是否添加成功
chkconfig --list

Docker安装Minio

docker上使用minio较多的镜像是bitnami/miniominio/minio ,他们都用于部署minio对象存储服务,但是存在以下区别:

维护者

  • minio/minio:是官方团队提供的镜像,包含minio运行环境和程序本身
  • bitnami/minio:是bitnami团队基于minio官方版本进行了封装和优化,提供一种更易于部署、管理和监控的解决方案

默认配置与管理工具

  • minio/minio 镜像需要手动通过命令行或环境变量来配置 MinIO 的访问密钥、机密密钥和其他参数。
  • bitnami/minio 提供了友好的默认配置,并且可能通过自定义的启动脚本来简化配置过程,同时 Bitnami 的容器通常会有一个更全面的应用生命周期管理工具集。

如果你希望获得一个更易部署和管理的 MinIO 部署方案,bitnami/minio 可能是一个更好的选择;而如果想要一个纯净、轻量级的 MinIO 实现,可以直接采用 minio/minio 镜像。

我们这里直接使用bitnami/minio

1、搜索镜像

docker search minio

image-20240818210341321

可以看到minio使用量比较多的bitnami团队的镜像

2、拉取镜像

docker pull bitnami/minio:2024.7.4

可以查看下镜像

docker images

3、运行镜像

先创建配置文件和存储数据的目录,用于将容器内的数据挂载到宿主机

## 创建存放minio配置文件的目录
mkdir -p /home/docker/minio/config
## 创建存放minio存储数据的目录
mkdir -p /home/docker/minio/data
docker run \
-p 9000:9000 \
-p 9001:9001 \
--net=host \
--name minio \
-d --restart=always \
-e "MINIO_ROOT_USER=minioadmin" \
-e "MINIO_ROOT_PASSWORD=minioadmin" \
-v /etc/timezone/timezone:/etc/timezone \
-v /etc/localtime:/etc/localtime:ro \
-v /home/minio/data:/bitnami/minio/data \
-v /home/minio/config:/root/.minio \
bitnami/minio:2024.7.4
注释解释:
-p 9000:9000	这是minio的API接口端口映射
-p 9001:9001	这是minio的客户端浏览器页面访问端口映射
--net=host	这是网络设置,表示容器将使用主机的网络栈
--name minio	自定义容器名称
-d --restart=always	-d 使容器在后台运行,–restart=always 表示容器总是会在退出后自动重启
-e "MINIO_ROOT_USER=minio" 	设置用户名
-e "MINIO_ROOT_PASSWORD=minioadmin" 	设置密码,密码长度最少8位,否则会报错!!!!!
-v /etc/timezone/timezone:/etc/timezone
-v /etc/localtime:/etc/localtime:ro	同步宿主机的时间
-v /home/minio/data:/bitnami/minio/data	 映射minio存储的数据文件到宿主机目录
-v /home/minio/config:/root/.minio 	映射minio的配置文件到宿主机目录
server /data:参数指定了MinIO服务器的配置。"/data"是MinIO服务器将用于存储数据的目录路径。

--console-address ":9000":指定MinIO控制台端口,确保任何尝试连接到这个端口的客户端或应用程序都可以使用这个端口

--address ":9001":指定MinIO服务端口,将Docker守护程序绑定到指定的端口上

如果遇到无法启动容器,是因为centos7.6中没有timezone文件,可以执行以下命令

echo 'Asia/Shanghai' > /etc/timezone/timezone

要确保挂载目录有权限哦,不然会报错。启动好docker后需等待一段时间后才可访问webUI,应该是minio启动需要配置很多东西。可以使用docker日志查看minio启动日志

docker logs 容器ID
日志内容:
minio 23:19:24.93 INFO  ==> ** Starting MinIO **
MinIO Object Storage Server
Copyright: 2015-2024 MinIO, Inc.
License: GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html
Version: DEVELOPMENT.2024-07-04T14-25-45Z (go1.21.12 linux/amd64)

API: http://localhost:9000 
WebUI: http://172.17.0.2:9001 http://127.0.0.1:9001   

Docs: https://min.io/docs/minio/linux/index.html
Status:         1 Online, 0 Offline. 
STARTUP WARNINGS:
- Detected Linux kernel version older than 4.0.0 release, there are some known potential performance problems with this kernel version. MinIO recommends a minimum of 4.x.x linux kernel version for best performance
- Detected default credentials 'minioadmin:minioadmin', we recommend that you change these values with 'MINIO_ROOT_USER' and 'MINIO_ROOT_PASSWORD' environment variables
- The standard parity is set to 0. This can lead to data loss.

 You are running an older version of MinIO released 1 month before the latest release 

使用初始密码可登录webUI,建议初始化后立即创建管理员用户。

Springboot集成Minio操作

minio中文文档:https://www.bookstack.cn/read/MinioCookbookZH/22.md

1、springboot的pom依赖

<dependencies>
        <!--spring-boot-starter-web 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.minio/minio -->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.4.0</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>provided</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/commons-lang/commons-lang -->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>

2、配置文件

minio:
  endpoint: http://192.168.0.200:9000 #Minio服务所在地址
  bucketName: mytest #存储桶名称
  accessKey: TAP1Ec9o9ROxNl6brDMJ #访问的key
  secretKey: 7MEy6GqsY90eopZvkBZQdiKDBhjGHrM00KLPkIb2 #访问的秘钥

3、Minio工具类

@Component
public class MinioUtil {
    @Autowired
    private MinioConfig prop;

    @Resource
    private MinioClient minioClient;

    /**
     * 查看存储bucket是否存在
     * @return boolean
     */
    public Boolean bucketExists(String bucketName) {
        Boolean found;
        try {
            found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return found;
    }

    /**
     * 创建存储bucket
     * @return Boolean
     */
    public Boolean makeBucket(String bucketName) {
        try {
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    /**
     * 删除存储bucket
     * @return Boolean
     */
    public Boolean removeBucket(String bucketName) {
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    /**
     * 获取全部bucket
     */
    public List<Bucket> getAllBuckets() {
        try {
            List<Bucket> buckets = minioClient.listBuckets();
            return buckets;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }



    /**
     * 文件上传
     *
     * @param file 文件
     * @return Boolean
     */
    public String upload(MultipartFile file) {
        String originalFilename = file.getOriginalFilename();
        if (StringUtils.isBlank(originalFilename)){
            throw new RuntimeException();
        }
        String fileName = generateUUIDWithoutDashes() + originalFilename.substring(originalFilename.lastIndexOf("."));
        String objectName = formatTodayDate("yyyy-MM/dd") + "/" + fileName;
        try {
            PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(prop.getBucketName()).object(objectName)
                    .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();
            //文件名称相同会覆盖
            minioClient.putObject(objectArgs);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return objectName;
    }

    /**
     * 预览图片
     * @param fileName
     * @return
     */
    public String preview(String fileName){
        // 查看文件地址
        GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket(prop.getBucketName()).object(fileName).method(Method.GET).build();
        try {
            String url = minioClient.getPresignedObjectUrl(build);
            return url;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 文件下载
     * @param fileName 文件名称
     * @param res response
     * @return Boolean
     */
    public void download(String fileName, HttpServletResponse res) {
        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(prop.getBucketName())
                .object(fileName).build();
        try (GetObjectResponse response = minioClient.getObject(objectArgs)){
            byte[] buf = new byte[1024];
            int len;
            try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()){
                while ((len=response.read(buf))!=-1){
                    os.write(buf,0,len);
                }
                os.flush();
                byte[] bytes = os.toByteArray();
                res.setCharacterEncoding("utf-8");
                // 设置强制下载不打开
                // res.setContentType("application/force-download");
                res.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
                try (ServletOutputStream stream = res.getOutputStream()){
                    stream.write(bytes);
                    stream.flush();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 查看文件对象
     * @return 存储bucket内文件对象信息
     */
    public List<Item> listObjects() {
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(prop.getBucketName()).build());
        List<Item> items = new ArrayList<>();
        try {
            for (Result<Item> result : results) {
                items.add(result.get());
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return items;
    }

    /**
     * 删除
     * @param fileName
     * @return
     * @throws Exception
     */
    public boolean remove(String fileName){
        try {
            minioClient.removeObject( RemoveObjectArgs.builder().bucket(prop.getBucketName()).object(fileName).build());
        }catch (Exception e){
            return false;
        }
        return true;
    }

    // 封装生成不带横杠UUID的方法
    public static String generateUUIDWithoutDashes() {
        String uuid = UUID.randomUUID().toString();
        return uuid.replaceAll("-", "");
    }

    // 封装格式化今天日期的方法
    public static String formatTodayDate(String format) {
        // 定义日期格式
        SimpleDateFormat formatter = new SimpleDateFormat(format);

        // 获取今天的日期
        Date today = new Date();

        // 格式化日期并返回
        return formatter.format(today);
    }

}

4、接口Controller

@Api(tags = "文件相关接口")
@Slf4j
@RestController
@RequestMapping(value = "product/file")
public class FileController {


    @Autowired
    private MinioUtil minioUtil;
    @Autowired
    private MinioConfig prop;

    @ApiOperation(value = "查看存储bucket是否存在")
    @GetMapping("/bucketExists")
    public R bucketExists(@RequestParam("bucketName") String bucketName) {
        return R.ok().put("bucketName",minioUtil.bucketExists(bucketName));
    }

    @ApiOperation(value = "创建存储bucket")
    @GetMapping("/makeBucket")
    public R makeBucket(String bucketName) {
        return R.ok().put("bucketName",minioUtil.makeBucket(bucketName));
    }

    @ApiOperation(value = "删除存储bucket")
    @GetMapping("/removeBucket")
    public R removeBucket(String bucketName) {
        return R.ok().put("bucketName",minioUtil.removeBucket(bucketName));
    }

    @ApiOperation(value = "获取全部bucket")
    @GetMapping("/getAllBuckets")
    public R getAllBuckets() {
        List<Bucket> allBuckets = minioUtil.getAllBuckets();
        return R.ok().put("allBuckets",allBuckets);
    }

    @ApiOperation(value = "文件上传返回url")
    @PostMapping("/upload")
    public R upload(@RequestParam("file") MultipartFile file) {
        String objectName = minioUtil.upload(file);
        if (null != objectName) {
            return R.ok().put("url",(prop.getEndpoint() + "/" + prop.getBucketName() + "/" + objectName));
        }
        return R.error();
    }

    @ApiOperation(value = "图片/视频预览")
    @GetMapping("/preview")
    public R preview(@RequestParam("fileName") String fileName) {
        return R.ok().put("filleName",minioUtil.preview(fileName));
    }

    @ApiOperation(value = "文件下载")
    @GetMapping("/download")
    public R download(@RequestParam("fileName") String fileName, HttpServletResponse res) {
        minioUtil.download(fileName,res);
        return R.ok();
    }

    @ApiOperation(value = "删除文件", notes = "根据url地址删除文件")
    @PostMapping("/delete")
    public R remove(String url) {
        String objName = url.substring(url.lastIndexOf(prop.getBucketName()+"/") + prop.getBucketName().length()+1);
        minioUtil.remove(objName);
        return R.ok().put("objName",objName);
    }

}
  • 14
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MinIO是一个开源的分布式对象存储服务,它可以在私有云环境提供高性能和高可用性的存储解决方案。下面是关于MinIO的一些基本概念和使用方法: 1. 分布式存储:MinIO使用分布式架构,可以将数据存储在多个节点上,实现数据的冗余和高可用性。每个节点都可以独立地提供存储服务,并且可以通过添加更多的节点来扩展存储容量和吞吐量。 2. 对象存储MinIO以对象的形式存储数据,每个对象都有一个唯一的键(Key)和对应的值(Value)。对象可以是任意类型的文件,例如图片、视频、文档等。通过使用对象存储MinIO可以提供高效的数据访问和管理。 3. 数据分片:MinIO将每个对象分成多个数据片段(Data Shard),并将它们分布在不同的节点上。这种数据分片的方式可以提高数据的可靠性和可用性,同时也可以提高数据的读写性能。 4. 冗余备份:MinIO使用纠删码(Erasure Code)技术来实现数据的冗余备份。纠删码可以将数据分成多个片段,并将这些片段分布在不同的节点上。即使某个节点发生故障,系统仍然可以通过其他节点上的数据片段进行数据恢复。 5. 客户端接口:MinIO提供了丰富的客户端接口,可以方便地与MinIO进行交互。你可以使用MinIO的命令行工具、API接口或者各种编程语言的SDK来管理和操作MinIO存储。 下面是一个使用MinIO Python SDK上传文件的例子: ```python from minio import Minio # 创建MinIO客户端 client = Minio('play.min.io', access_key='YOUR_ACCESS_KEY', secret_key='YOUR_SECRET_KEY', secure=True) # 上传文件 client.fput_object('mybucket', 'myobject', 'path/to/local/file.jpg') # 关闭客户端连接 client.close() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值