Minio独立文件服务集群 无效附件处理

背景

视频、图片展现,生成缩略图,点击缩略图查看原文件

架构

系统架构如下图。文件存储到独立的文件服务,解决在服务端集群环境下文件无法集群问题。客户端通过服务端将文件发送到文件服务集群,浏览时直接访问文件服务集群

MinIO

简介

MinIO 是高性能的对象存储,是为海量数据存储、人工智能、大数据分析而设计的,单个对象最大可达5TB,适合存储海量图片、视频、日志文件、备份数据和容器/虚拟机镜像等。MinIO主要采用Golang语言实现,,客户端与存储服务器之间采用http/https通信协议。

Minio的架构采用了分布式的设计,它可以将数据分散存储在多个节点中,从而实现数据的高可用和容错性。

低冗余且磁盘损坏高容忍,标准且最高的数据冗余系数为2(即存储一个1M的数据对象,实际占用 磁盘空间为2M)。但在任意n/2块disk损坏的情况下依然可以读出数据(n为一个纠删码集合(Erasure Coding Set)中的disk数量)。并且这种损坏恢复是基于单个对象的,而不是基于整个存储卷的。 读写性能优异。

Server

在Minio中,节点被称为Minio Server,每个Minio Server可以存储一个或多个对象存储桶。对象存储桶是一组对象的集合,类似于文件系统中的文件夹。每个对象存储桶都有一个唯一的名称,它可以在Minio集群中全局唯一。

程序使用MinIO可只安装Server。

Server通过提供RESTful API实现Minio的数据访问,它可以提供各种数据管理功能,如创建、删除、读取、写入对象等。

Client

Minio Client是一个命令行工具,它提供了与Minio Server交互的API,可以使用它来创建、删除、上传、下载对象等操作。

MinIO 客户端 mc 命令行工具提供了一种现代的替代 UNIX 命令, 如 ls、cat、cp、mirror 和 diff 支持文件系统和兼容 Amazon S3 的云存储服务(AWS Signature v2 和 v4)。

术语

Object

存储到 Minio 的基本对象,如文件、字节流,Anything...

Bucket

MinIO 对象存储使用 buckets 来组织对象。 存储桶类似于文件系统中的文件夹或目录,其中每个 桶可以容纳任意数量的对象。

Drive

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

Set

即一组 Drive 的集合,分布式部署根据集群规模自动划分一个或多个 Set ,每个 Set 中的 Drive 分布在不同位置。一个对象存储在一个 Set 上。(For example: {1…64} is pided into 4 sets each of size 16.) 一个对象存储在一个Set上 一个集群划分为多个Set 一个Set包含的Drive数量是固定的,默认由系统根据集群规模自动计算得出 一个SET中的Drive尽可能分布在不同的节点上

部署方式

mkdir /data/minio/data /data/minio/log
mkdir /opt/apps/minio
cd /opt/apps/minio
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
MINIO_ROOT_USER=admin MINIO_ROOT_PASSWORD=12345678 ./minio server /data/minio/data > /data/minio/log/minio.log --console-address ":9001"


如果不指定管理界面端口,minio随机生成端口,浏览器直接访问http://ip:9000会跳转到随机端口
可将下列脚本写入server-start.sh脚本,方便启动

#!/bin/bash
export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=12345678
./minio server /data/minio/data > /data/minio/log/minio.log  --console-address ":9001" &


Java程序使用minio
maven pom.xml增加依赖

 
        
            io.minio
            minio
            8.0.3
        
 
        
            org.bytedeco
            javacv
            1.4.3
        
        
            org.bytedeco.javacpp-presets
            ffmpeg-platform
            4.0.2-1.4.3
        
        
            org.apache.commons
            commons-lang3
            3.7
        
 
        
            commons-io
            commons-io
            2.11.0
        


application.yml配置中增加minio地址,上传文件大小限制

spring:
  # 配置文件上传大小限制
  servlet:
    multipart:
      max-file-size: 200MB
      max-request-size: 200MB
minio:
  endpoint: http://192.168.56.41:9000
  accessKey: admin
  secretKey: 12345678
  bucketName: test-private


读取配置,构建MinioClient

import io.minio.MinioClient;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Data
@Component
public class MinIoClientConfig {
    @Value("${minio.endpoint}")
 private String endpoint;
    @Value("${minio.accessKey}")
 private String accessKey;
    @Value("${minio.secretKey}")
 private String secretKey;
    @Value("${minio.bucketName}")
 private String bucketName;
 /**
 * 注入minio 客户端
     * @return
 */
    @Bean
 public MinioClient minioClient(){
 return MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .build();
    }
}


公共工具类,实现创建桶、文件上传、获取预览链接、删除文件、生成视频图片的缩略图等

import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.FastByteArrayOutputStream;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
 * @description:
 */
@Component
@Slf4j
@RequiredArgsConstructor
public class MinioUtil {
    @Autowired
    final MinIoClientConfig prop;
    @Autowired
    final MinioClient minioClient;
 private static final String THUMBNAIL_FORMAT = "jpg";
 public static final String THUMBNAIL_NAME_SUFFIX = "_thumbnail.jpg";
 private static final int THUMBNAIL_SIZE = 300;
 private static final String THUMBNAIL_CONTENT_TYPE = "image/jpeg";
 private Boolean bucketExists;
 /**
 * 查看存储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 getAllBuckets() {
 try {
            List 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;
 if (originalFilename.indexOf(".") > 0) {
            fileName = originalFilename.substring(0, originalFilename.lastIndexOf("."))
                    +  "_" + System.currentTimeMillis() + "."
                    + originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
        } else {
            fileName = originalFilename + System.currentTimeMillis();
        }
 //对象名称:年月日/文件名_milliseconds.suffix
        String dateStr = LocalDate.now().format(DateTimeFormatter.ISO_DATE);
        String objectName =  dateStr + "/" + fileName;
 try {
 if (bucketExists == null) {
                bucketExists = bucketExists(prop.getBucketName());
            }
 if (bucketExists == false) {
                makeBucket(prop.getBucketName());
            }
            PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(prop.getBucketName()).object(objectName)
                    .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();
 //文件名称相同会覆盖
            minioClient.putObject(objectArgs);
            int fileType = getFileType(originalFilename);
 //生成图片缩略图
 if (fileType == 1) {
                thumbnailImage(file.getInputStream(), dateStr, fileName, true);
            } else if  (fileType == 0) {
                thumbnailVideo(file.getInputStream(), dateStr, fileName, true);
            }
        } 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 listObjects() {
        Iterable> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(prop.getBucketName()).build());
        List items = new ArrayList<>();
 try {
 for (Result result : results) {
                Item item = result.get();
 if (item.isDir()) {
                    totalFile(item.objectName(), items);
                }
                items.add(new ObjectItem(item.objectName(), item.size()));
            }
        } catch (Exception e) {
            e.printStackTrace();
 return null;
        }
 return items;
    }
 //递归取得全部文件
 public void totalFile(String folder, List list) {
 try {
            Iterable> listObjects = minioClient.listObjects(ListObjectsArgs.builder()
                    .bucket(prop.getBucketName()).prefix(folder).build());
 for (Result result : listObjects) {
                Item item = result.get();
 if (item.isDir()) {
 //最后一位是/则为文件夹
                    list.add(new ObjectItem(item.objectName(), item.size()));
                    totalFile(item.objectName(), list);
                }else {
                    list.add(new ObjectItem(item.objectName(), item.size()));
                }
            }
        } catch (Exception e) {
            System.err.println(e.toString());
        }

    }
 /**
 * 删除
     * @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;
    }
 /**
 * 保存视频缩略图
     * @param videoInput 视频文件输入流
 * @param dateStr 日期,用于生成minio对象名称
 * @param videoFileName 视频文件对象名,minio中文件对象名,不带日期
 * @param force      是否强制按照宽高生成缩略图(如果为false,则生成最佳比例缩略图)
 * @return
 * @throws Exception
 */
 public String thumbnailVideo(InputStream videoInput, String dateStr, String videoFileName, boolean force) {
 try {
 //保存的目标路径,精确到文件
            String framefileName;
 if (videoFileName.indexOf(".") > 0) {
                framefileName = videoFileName.substring(0, videoFileName.lastIndexOf(".")) + THUMBNAIL_NAME_SUFFIX;
            } else {
                framefileName = videoFileName + THUMBNAIL_NAME_SUFFIX;
            }
            File thumbnailFile = new File(framefileName);
            FFmpegFrameGrabber ff = new FFmpegFrameGrabber(videoInput);
            FileInputStream thumbnailInput= null;
 try {
                ff.start();
                int length = ff.getLengthInFrames();
                int i = 0;
                Frame f = null;
 while (i < length) {
 // 取第1帧
                    f = ff.grabFrame();
 if ((i >= 0) && (f.image != null)) {
 break;
                    }
                    i++;
                }
                int width = THUMBNAIL_SIZE;
                int height = THUMBNAIL_SIZE;
 // 对截取的帧进行等比例缩放
 if (!force) {
                    int owidth = f.imageWidth;
                    int oheight = f.imageHeight;
                    height = (int) (((double) width / owidth) * oheight);
                }
                Java2DFrameConverter converter = new Java2DFrameConverter();
                BufferedImage fecthedImage = converter.getBufferedImage(f);
//                BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
                BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);//imageType对图片效果影响不大,width、height影响较大
                bi.getGraphics().drawImage(fecthedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH),
                        0, 0, null);
                ImageIO.write(bi, THUMBNAIL_FORMAT, thumbnailFile);
                thumbnailInput = new FileInputStream(thumbnailFile);
                PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(prop.getBucketName()).object(dateStr + "/" + framefileName).stream(thumbnailInput, thumbnailFile.length(), 1).contentType(THUMBNAIL_CONTENT_TYPE).build();
 //文件名称相同会覆盖
                minioClient.putObject(objectArgs);
 return framefileName;
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("ImgBase64Util fetchFrame() error.");
            } finally {
                ff.stop();
                ff.close();
                IOUtils.close(thumbnailInput);
                thumbnailFile.delete(); //删除本地缩略图文件
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
 return null;
    }
 /**
 * 

Title: thumbnailImage

*

Description: 根据图片路径生成缩略图

* @param imgInput 图片输入流 * @param dateStr 日期,用于生成minio对象名称 * @param imgFileName 图片文件名,不带日期 * @param force 是否强制按照宽高生成缩略图(如果为false,则生成最佳比例缩略图) */ public String thumbnailImage(InputStream imgInput, String dateStr, String imgFileName, boolean force){ String thumImgFileName; if (imgFileName.indexOf(".") > 0) { thumImgFileName = imgFileName.substring(0, imgFileName.lastIndexOf(".")) + THUMBNAIL_NAME_SUFFIX; } else { thumImgFileName = imgFileName + THUMBNAIL_NAME_SUFFIX; } File thumbnailFile = new File(thumImgFileName); FileInputStream thumbnailInput= null; int w = THUMBNAIL_SIZE; int h = THUMBNAIL_SIZE; try { // ImageIO 支持的图片类型 : [BMP, bmp, jpg, JPG, wbmp, jpeg, png, PNG, JPEG, WBMP, GIF, gif] String types = Arrays.toString(ImageIO.getReaderFormatNames()); String suffix = null; // 获取图片后缀 if(thumbnailFile.getName().indexOf(".") > -1) { suffix = thumbnailFile.getName().substring(thumbnailFile.getName().lastIndexOf(".") + 1); } // 类型和图片后缀全部小写,然后判断后缀是否合法 if(suffix == null || types.toLowerCase().indexOf(suffix.toLowerCase()) < 0){ log.warn("Sorry, the image suffix is illegal. the standard image suffix is {}." + types); return null; } //log.debug("target image's size, width:{"+w+"}, height:{"+h+"}."); Image img = ImageIO.read(imgInput); // 根据原图与要求的缩略图比例,找到最合适的缩略图比例 int width = img.getWidth(null); int height = img.getHeight(null); if(!force){ if((width*1.0)/w < (height*1.0)/h){ if(width > w){ h = Integer.parseInt(new java.text.DecimalFormat("0").format(height * w/(width*1.0))); //System.out.println("change image's height, width:{"+w+"}, height:{"+h+"}."); } } else { if(height > h){ w = Integer.parseInt(new java.text.DecimalFormat("0").format(width * h/(height*1.0))); //System.out.println("change image's width, width:{"+w+"}, height:{"+h+"}."); } } } BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); Graphics g = bi.getGraphics(); g.drawImage(img, 0, 0, w, h, Color.LIGHT_GRAY, null); g.dispose(); // 将图片保存在原目录并加上前缀 ImageIO.write(bi, THUMBNAIL_FORMAT, thumbnailFile); //System.out.println("缩略图在原路径下生成成功"); thumbnailInput = new FileInputStream(thumbnailFile); PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(prop.getBucketName()).object(dateStr + "/" + thumImgFileName).stream(thumbnailInput, thumbnailFile.length(), -1).contentType(THUMBNAIL_CONTENT_TYPE).build(); //文件名称相同会覆盖 minioClient.putObject(objectArgs); } catch (Exception e) { log.warn("generate thumbnail image failed."+e); return null; } finally { try { IOUtils.close(thumbnailInput); thumbnailFile.delete(); } catch (IOException e) { e.printStackTrace(); } } return thumImgFileName; } /** * 鉴定文件是图片还是视频 * @Param fileName * @return 1-图片;0-视频 **/ public int getFileType(String fileName) { int i = 0; FileNameMap fileNameMap = URLConnection.getFileNameMap(); String contentTypeFor = fileNameMap.getContentTypeFor(fileName); if (contentTypeFor != null) {// 当是图片时不为空,是视频时为空 i = 1; } return i; } } 返回列表自定义对象 import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class ObjectItem { private String objectName; private Long size; }


清理无效附件
在业务应用中,选择图片、视频之后后端会将文件上传到文件服务器,并使用特定标识对应该文件。业务表单保存时一并存储该标识。实际使用中,用户可能选择了图片视频后并未保存业务表单,导致文件服务器里的文件没有关联业务,我们称这些文件为无效附件,若不清理则将导致无效附件越来越多,占据文件服务器存储空间,而文件又永远使用不到。尤其博客类图片、视频附件特别多,必须清理无效附件。
因此,特地规划如下附件-业务表单存储流程。

临时表设计

这样保证了minio中的文件对象都是有业务关联的有效附件。

至此独立文件服务器的搭建和使用已介绍完毕,满足一般的附件应用。


分布式部署

对于高可用要求的文件服务,推荐分布式部署。

生产环境建议至少四台机器,这样挂掉一台机器集群依然可以读写,挂掉两台机器集群依然可读。本文仅以两台机器为例子说明搭建集群 ,挂掉一台能读不能写,每台机器2个数据目录(必须,否则报错 Error: Read failed. Insufficient number of drives online)

ip

name

备注

192.168.56.41

minio01

数据目录:/minio/data1 /minio/data2

192.168.56.42

minio02

数据目录:/minio/data1 /minio/data2

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

数据保护

高可用

单机Minio服务存在单点故障,相反,如果是一个有N块硬盘的分布式Minio,只要有N/2硬盘在线,你的数据就是安全的。不过你需要至少有N/2+1个硬盘来创建新的对象。

例如,一个16节点的Minio集群,每个节点16块硬盘,就算8台服务器宕机,这个集群仍然是可读的,不过你需要9台服务器才能写数据。

一致性

Minio在分布式和单机模式下,所有读写操作都严格遵守read-after-write一致性模型。

挂载硬盘

集群时minio 数据目录一定不能和 跟/ 在一个磁盘上,要单独挂载,否则报错,Error: Drive /data/minio/data1 is part of root drive, will not be used

1、停止VirtualBox虚拟机并添加虚拟硬盘



创建虚拟硬盘,大小50GB,动态扩容



2、启动虚拟机,进入命令行

fdisk -l /dev/sdb
fdisk /dev/sdb
命令(输入 m 获取帮助):n                      # n 创建分区,
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): p                        # n 创建主分区
分区号 (1-4,默认 1):                        #  直接回车,走默认1
起始 扇区 (2048-4194303,默认为 2048):       # 直接回车,从默认2048开始
将使用默认值 2048
Last 扇区, +扇区 or +size{K,M,G} (2048-4194303,默认为 4194303):   # 直接回车,说明有多少做多少G硬盘
将使用默认值 4194303
分区 1 已设置为 Linux 类型,大小设为 2 GiB

命令(输入 m 获取帮助):p     # 打印

磁盘 /dev/sdb:2147 MB, 2147483648 字节,4194304 个扇区
Units = 扇区 of 1 * 512 = 512 bytes
扇区大小(逻辑/物理):512 字节 / 512 字节
I/O 大小(最小/最佳):512 字节 / 512 字节
磁盘标签类型:dos
磁盘标识符:0x23a6127b

   设备 Boot      Start         End      Blocks   Id  System
/dev/sdb1            2048     4194303     2096128   83  Linux

命令(输入 m 获取帮助):w      # 保存分区
The partition table has been altered!

Calling ioctl() to re-read partition table.
正在同步磁盘。

[root@master01 ~]# ll /dev/sdb*
brw-rw---- 1 root disk 8, 16 4月   7 04:43 /dev/sdb
brw-rw---- 1 root disk 8, 17 4月   7 04:43 /dev/sdb1

[root@master01 ~]# mkfs.xfs  /dev/sdb1   # 格式化分区
meta-data=/dev/sdb1              isize=512    agcount=4, agsize=131008 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=0, sparse=0
data     =                       bsize=4096   blocks=524032, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0 ftype=1
log      =internal log           bsize=4096   blocks=2560, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0

[root@master01 ~]# blkid           # 查看/dev/sdb1的uuid
/dev/sda1: UUID="c219aeb3-fb5b-4009-9e9c-e3396a36ea3b" TYPE="xfs"
/dev/sda2: UUID="B6PX7v-uSkg-08db-w9V3-TT31-x7x5-ehMsr4" TYPE="LVM2_member"
/dev/sdb1: UUID="1f00db8b-dc34-4528-93ad-043192739a20" TYPE="xfs"
/dev/mapper/centos-root: UUID="243723b9-2e4d-40a6-aed9-23e227d61572" TYPE="xfs"
/dev/mapper/centos-swap: UUID="9ded6aff-9d46-4b84-abb9-1de8691c5bf7" TYPE="swap"

[root@master01 ~]# echo "UUID=1f00db8b-dc34-4528-93ad-043192739a20 /minio xfs defaults 0 0 " >> /etc/fstab  # 将/dev/sdb1 挂载到 /minio

[root@master01 ~]# tail -n 2 /etc/fstab
#UUID=e27dc238-c4d9-4921-81a0-53d7002f0e33 /opt/yyiuap/  xfs defaults 0 0
UUID=1f00db8b-dc34-4528-93ad-043192739a20 /data/minio xfs defaults 0 0

[root@master01 ~]# mkdir /minio    # 创建挂载目录,否则mount -a 失败

[root@master01 ~]# mount -a    # 读取/etc/fstab 文件重新挂载

[root@master01 ~]# df -h
文件系统                 容量  已用  可用 已用% 挂载点
/dev/mapper/centos-root   48G   11G   38G   22% /
devtmpfs                 899M     0  899M    0% /dev
tmpfs                    911M     0  911M    0% /dev/shm
tmpfs                    911M  9.6M  902M    2% /run
tmpfs                    911M     0  911M    0% /sys/fs/cgroup
/dev/sda1               1014M  142M  873M   14% /boot
tmpfs                    183M     0  183M    0% /run/user/0
/dev/sdb1                2.0G   33M  2.0G    2% /data/minio     # 发现已经挂载

集群配置

1、创建数据目录和日志目录

mkdir /minio/data1 /minio/data2 /minio/log

2、创建启动脚本文件

vim /opt/apps/minio/cluster-start.sh

服务端口9000,控制界面端口9001,--config-dir:指定集群配置文件目录

#!/bin/bash
export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=12345678
/opt/apps/minio/minio server --config-dir /etc/minio 
        --address "0.0.0.0:9000" --console-address ":9001" 
        http://192.168.56.41:9000/minio/data1 http://192.168.56.41:9000/minio/data2 
        http://192.168.56.42:9000/minio/data1 http://192.168.56.42:9000/minio/data2 >> /minio/log/minio.log

3、创建停止脚本文件

vim /opt/apps/minio/stop.sh
#!/bin/bash
#MinIO停止脚本
pid=$(ps -ef|grep minio|grep -v grep | awk '{print $2}')
function stop(){
    if [ -n "$pid" ]
    then
        echo "pid进程 :$pid"
        kill -9 $pid
    else
        echo "进程没有启动"
    fi
}
stop

4、创建Minio.service

cat < /etc/systemd/system/minio.service
[Unit]
Description=Minio service
Documentation=https://docs.minio.io/

[Service]
WorkingDirectory=/opt/apps/minio/
ExecStart=/opt/apps/minio/cluster-start.sh
ExecStop=/opt/apps/minio/stop.sh

Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

5、权限修改

chmod +x /etc/systemd/system/minio.service && chmod +x /opt/apps/minio/cluster-start.sh && chmod +x /opt/apps/minio/stop.sh

6、启动集群

systemctl daemon-reload
systemctl start minio
systemctl enable minio
systemctl status minio.service

7、查看,在浏览器输入http://192.168.56.41:9001/,点击Monitoring-Metrics菜单,可看到2个节点,4个Drive(磁盘)



8、验证集群,使用前面的Java程序,往192.168.56.41上传一个wmv视频文件,同时生成缩略图。可以在两台机器的4个目录都看到这两个文件




至此集群部署Minio方式已基本完成,实现了文件集群高可用。但API服务还未实现负载均衡,总是访问一台固定的Minio API服务,存在服务故障而导致集群无法访问的问题。

接下来介绍使用nginx实现API服务的负载均衡访问。

Nginx反向代理与负载均衡

安装nginx

1、官网下载nginx,上传到虚拟机minio01上

http://nginx.org/en/download.html

2、解压,进入nginx,配置

tar -xzf nginx-1.24.0.tar.gz
 mv nginx-1.24.0 nginx
 cd nginx/
 #指定 nginx 的安装路径为 /usr/local/nginx,同时启用了 SSL 和状态监控模块。
 ./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-http_stub_status_module --with-pcre

在新装的centos7上面安装nginx到时候,执行./config 时候 出现错误。

checking for OS
 + Linux 3.10.0-1127.el7.x86_64 x86_64
checking for C compiler ... not found

./configure: error: C compiler cc is not found

执行下列命令安装gcc

yum -y install gcc gcc-c++ autoconf automake make


#再次配置,报错

./configure: error: the HTTP rewrite module requires the PCRE library.
You can either disable the module by using --without-http_rewrite_module
option, or install the PCRE library into the system, or build the PCRE library
statically from the source with nginx by using --with-pcre= option.

安装pcre-devel

yum install -y pcre-devel

再次配置,报错

./configure: error: SSL modules require the OpenSSL library. You can either do not enable the modules, or install the OpenSSL library into the system, or build the OpenSSL library statically from the source with nginx by using --with-openssl= option.


#安装openssl

yum -y install openssl openssl-devel

再次配置,成功

Configuration summary
  + using system PCRE library
  + using system OpenSSL library
  + using system zlib library

  nginx path prefix: "/usr/local/nginx"
  nginx binary file: "/usr/local/nginx/sbin/nginx"
  nginx modules path: "/usr/local/nginx/modules"
  nginx configuration prefix: "/usr/local/nginx/conf"
  nginx configuration file: "/usr/local/nginx/conf/nginx.conf"
  nginx pid file: "/usr/local/nginx/logs/nginx.pid"
  nginx error log file: "/usr/local/nginx/logs/error.log"
  nginx http access log file: "/usr/local/nginx/logs/access.log"
  nginx http client request body temporary files: "client_body_temp"
  nginx http proxy temporary files: "proxy_temp"
  nginx http fastcgi temporary files: "fastcgi_temp"
  nginx http uwsgi temporary files: "uwsgi_temp"
  nginx http scgi temporary files: "scgi_temp"

3、安装

make && make install

4、启动

#添加软连接,以后可在任意目录使用nginx命令
ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx
#启动
nginx
#重启
nginx -s reload

浏览器访问http://192.168.56.41/,直接访问nginx欢迎页面



5、开机启动

方法一

1.找到/etc/rc.local文件,在文件最后一行新增启动命令(nginx默认安装目录为:/usr/local/nginx):

/usr/local/nginx/sbin/nginx

方法二(推荐,方便指定启动时机)

(1).进入/etc/systemd/system文件夹,新增文件 nginx.service (2).文件内容:

[Unit]
Description=nginx service
After=network.target

[Service]
User=root
Type=forking
ExecStart=/usr/local/nginx/sbin/nginx
ExecReload=/usr/local/nginx/sbin/nginx -s reload
ExecStop=/usr/local/nginx/sbin/nginx -s stop
ExecStartPre=/bin/sleep 10

[Install]
WantedBy=multi-user.target

(3).开启开机启动

chmod +x /etc/systemd/system/nginx.service
systemctl enable nginx
nginx -s stop
systemctl start nginx

负载均衡

1、配置nginx.conf,http下增加

upstream  minio_server {
        server 192.168.56.41:9000 weight=1;
        server 192.168.56.42:9000 weight=1; 
}
server {
        listen       9100;
        server_name  localhost;
        charset utf-8;
        location /{
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-For $remote_addr;
            client_body_buffer_size 10M;
            client_max_body_size 1G;
            proxy_buffers 1024 4k;
            proxy_read_timeout 300;
            proxy_next_upstream error timeout http_404;
            proxy_pass http://minio_server;
        }
}

2、修改application.yml中minio.endpoint指向9100端口(nginx端口)

minio:
  endpoint: http://192.168.56.41:9100

3、重启程序,实现对minio api的负载均衡

至此完成minio集群搭建。

参考文献

Minio基本介绍及如何搭建Minio集群 (nginx https配置详细)

minio 部署、迁移、使用 (minio 数据目录一定不能和 跟/ 在一个磁盘上,要单独挂载,否则Error: Drive /data/minio/data1 is part of root drive, will not be used;分https和http启动minio)

Minio教程 (nginx https配置不详细;用mc进行数据迁移)

minio分布式部署-高可用架构

CentOS7操作系统安装nginx实战(多种方法,超详细)

linux配置nginx开机启动

Nginx单独开启SSL模块和HTTP2模块,无需重新覆盖安装Nginx如果未开启SSL模块,配置Https时提示错误

JDK导入ssl证书

浏览器显示“SSL证书无效”怎么办(SSL证书不是由受信的CA机构所签发的。)

关于SSL认证的小坑 SSLPeerUnverifiedException

生成带subjectAltName的ssl服务端证书【亲测有效】(SSLPeerUnverifiedException: Hostname minio.kunsam.com not verified:问题解决)

Java实现minio上传、下载、删除文件,支持https访问 (取消SSL认证)

完全卸载nginx及安装的详细步骤

展开阅读全文

页面更新:2024-02-15

标签:集群   附件   文件   分布式   磁盘   缩略图   对象   独立   目录   数据   图片

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top