Minio独立文件服务集群 无效附件处理
liebian365 2024-10-26 13:05 16 浏览 0 评论
背景
视频、图片展现,生成缩略图,点击缩略图查看原文件
架构
系统架构如下图。文件存储到独立的文件服务,解决在服务端集群环境下文件无法集群问题。客户端通过服务端将文件发送到文件服务集群,浏览时直接访问文件服务集群
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 divided into 4 sets each of size 16.) 一个对象存储在一个Set上 一个集群划分为多个Set 一个Set包含的Drive数量是固定的,默认由系统根据集群规模自动计算得出 一个SET中的Drive尽可能分布在不同的节点上
部署方式
- 单机部署
- 分布式部署
单机部署
安装与启动
官方文档 https://min.io/download
创建 /data/minio/data,/data/minio/log目录分别存放minio文件和日志
mkdir /data/minio/data /data/minio/log
创建/opt/apps/minio目录存放minio程序
mkdir /opt/apps/minio
cd /opt/apps/minio
下载minio server文件,赋予可执行权限,设置根用户账号密码admin/password并指定网页管理控制界面端口为9001,Drive(数据磁盘)指定为 /data/minio/data 。
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_ACCESS_KEY和MINIO_SECRET_KEY设置用户名及密码的参数名过时了,最新的参数为MINIO_ROOT_USER、MINIO_ROOT_PASSWORD。
浏览器访问http://ip:9001即可访问minio管理控制界面
如果不指定管理界面端口,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增加依赖
<!--minio-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.0.3</version>
</dependency>
<!-- 生成缩略图 -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.4.3</version>
</dependency>
<dependency>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>ffmpeg-platform</artifactId>
<version>4.0.2-1.4.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
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<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;
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<ObjectItem> listObjects() {
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs.builder().bucket(prop.getBucketName()).build());
List<ObjectItem> items = new ArrayList<>();
try {
for (Result<Item> 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<ObjectItem> list) {
try {
Iterable<Result<Item>> listObjects = minioClient.listObjects(ListObjectsArgs.builder()
.bucket(prop.getBucketName()).prefix(folder).build());
for (Result<Item> 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;
}
/**
* <p>Title: thumbnailImage</p>
* <p>Description: 根据图片路径生成缩略图 </p>
* @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对象名fileObjectName存储到临时表filesTmpTable,并返回到前端
- 前端提交表单含fileObjectName到后端,后端保存业务表单,同时从临时表filesTmpTable删除fileObjectName对应的数据记录
- 若前端不提交表单,则fileObjectName存储在临时表filesTmpTable而不会被删除
- 经过前述处理,临时表filesTmpTable中长时间存储的都是无效附件,启动一个定时任务,每天删除filesTmpTable中创建时间在1天前的数据记录和对应的minio文件
临时表设计
这样保证了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 |
分布式存储,很关键的点在于数据的可靠性,即保证数据的完整不丢失,不损坏,只有在可靠性实现的前提下,才有了追求一致性、高可用、高性能的基础。而对于在存储领域,一般对于保证数据可靠性的方法主要有两类,一类是冗余法,一类是校验法。
- 冗余法最简单直接,即对存储的数据进行副本备份,当数据出现丢失,损坏,即可使用备份内容进行恢复,而副本备份的多少,决定了 数据可靠性的高低,这其中会有成本的考量,副本数据越多,数据越可靠,但需要的设备就越多,成本就越高,可靠性是允许丢失其中一 份数据,当前已有很多分布式系统是采用此种方式实现,如 Hadoop 的文件系统 (3个副本),Redis 的集群,MySQL 的主备模式等。
- 校验法即通过校验码的数学计算的方式,对出现丢失、损坏的数据进行校验、还原。注意,这里有两个作用,一个校验,通过对数据进行校验和(checksum)进行计算,可以检查数据是否完整,有无损坏或更改,在数据传输和保存时经常用到,如TCP协议;二是恢复还原, 通过对数据结合校验码,通过数学计算,还原丢失或损坏的数据,可以在保证数据可靠的前提下,降低冗余,如单机硬盘存储中的RAID 技术,纠删码(Erasure Code) 技术等。MinlO采用的就是纠删码技术。
数据保护
- 分布式Minio采用纠删码来防范多个节点宕机和位衰减bit rot。
- 分布式Minio至少需要4个硬盘,使用分布式Minio自动引入了纠删码功能。
高可用
单机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、启动虚拟机,进入命令行
- n创建分区后一路默认,直到w保存分区
- mkfs.xfs /dev/sdb1 格式化分区
- 将/dev/sdb1 挂载到 /minio
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 <<EOF > /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=<path> 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及安装的详细步骤
相关推荐
- 4万多吨豪华游轮遇险 竟是因为这个原因……
-
(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...
- “菜鸟黑客”必用兵器之“渗透测试篇二”
-
"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...
- 科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白
-
作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...
- 麦子陪你做作业(二):KEGG通路数据库的正确打开姿势
-
作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...
- 知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势
-
智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...
- 每日新闻播报(September 14)_每日新闻播报英文
-
AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...
- 香港新巴城巴开放实时到站数据 供科技界研发使用
-
中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...
- 5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper
-
本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...
- Qt动画效果展示_qt显示图片
-
今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...
- 如何从0到1设计实现一门自己的脚本语言
-
作者:dong...
- 三年级语文上册 仿写句子 需要的直接下载打印吧
-
描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...
- C++|那些一看就很简洁、优雅、经典的小代码段
-
目录0等概率随机洗牌:1大小写转换2字符串复制...
- 二年级上册语文必考句子仿写,家长打印,孩子照着练
-
二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)