Minio独立文件服务集群 无效附件处理
suiw9 2024-11-14 19:10 41 浏览 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及安装的详细步骤
相关推荐
- 5款Syslog集中系统日志常用工具对比推荐
-
一、为何要集中管理Syslog?Syslog由Linux/Unix系统及其他网络设备生成,广泛分布于整个网络。因其包含关键信息,可用于识别网络中的恶意活动,所以必须对其进行持续监控。将Sys...
- 跨平台、多数据库支持的开源数据库管理工具——DBeaver
-
简介今天给大家推荐一个开源的数据库管理工具——DBeaver。它支持多种数据库系统,包括Mysql、Oracle、PostgreSQL、SLQLite、SQLServer等。DBeaver的界面友好...
- 强烈推荐!数据库管理工具:Navicat Premium 16.3.2 (64位)
-
NavicatPremium,一款集数据迁移、数据库管理、SQL/查询编辑、智能设计、高效协作于一体的全能数据库开发工具。无论你是MySQL、MariaDB、MongoDB、SQLServer、O...
- 3 年 Java 程序员还玩不转 MongoDB,网友:失望
-
一、什么场景使用MongoDB?...
- 拯救MongoDB管理员的GUI工具大赏:从菜鸟到极客的生存指南
-
作为一名在NoSQL丛林中披荆斩棘的数据猎人,没有比GUI工具更称手的瑞士军刀了。本文将带你围观五款主流MongoDB管理神器的特性与暗坑,附赠精准到扎心的吐槽指南一、MongoDBCompass:...
- mongodb/redis/neo4j 如何自己打造一个 web 数据库可视化客户端?
-
前言最近在做neo4j相关的同步处理,因为产线的可视化工具短暂不可用,发现写起来各种脚本非常麻烦。...
- solidworks使用心得,纯干货!建议大家收藏
-
SolidWorks常见问题...
- 统一规约-关乎数字化的真正实现(规范统一性)
-
尽管数字化转型的浪潮如此深入人心,但是,对于OPCUA和TSN的了解却又甚少,这难免让人质疑其可实现性,因为,如果缺乏统一的语义互操作规范,以及更为具有广泛适用的网络与通信,则数字化实际上几乎难以具...
- Elasticsearch节点角色配置详解(Node)
-
本篇文章将介绍如下内容:节点角色简介...
- 产前母婴用品分享 篇一:我的母婴购物清单及单品推荐
-
作者:DaisyH8746在张大妈上已经混迹很久了,有事没事看看“什么值得买”已渐渐成了一种生活习惯,然而却从来没有想过自己要写篇文章发布上来,直到由于我产前功课做得“太过认真”(认真到都有点过了,...
- 比任何人都光彩照人的假期!水润、紧致的肌肤护理程序
-
图片来源:谜尚愉快的假期临近了。身心振奋的休假季节。但是不能因为这种心情而失去珍贵的东西,那就是皮肤健康。炙热的阳光和强烈的紫外线是使我们皮肤老化的主犯。因此,如果怀着快乐的心情对皮肤置之不理,就会使...
- Arm发布Armv9边缘AI计算平台,支持运行超10亿参数端侧AI模型
-
中关村在线2月27日消息,Arm正式发布Armv9边缘人工智能(AI)计算平台。据悉,该平台以全新的ArmCortex-A320CPU和领先的边缘AI加速器ArmEthos-U85NPU为核心...
- 柔性——面向大规模定制生产的数字化实现的基本特征
-
大规模定制生产模式的核心是柔性,尤其是体现在其对定制的要求方面。既然是定制,并且是大规模的定制,对于制造系统的柔性以及借助于数字化手段实现的柔性,就提出了更高的要求。面向大规模定制生产的数字化业务管控...
- 创建PLC内部标准——企业前进的道路
-
作者:FrankBurger...
- 标准化编程之 ----------- 西门子LPMLV30测试总结
-
PackML乃是由OMAC开发且被ISA所采用的自动化标准TR88.00.02,能够更为便捷地传输与检索一致的机器数据。PackML的主要宗旨在于于整个工厂车间倡导通用的“外观和感觉”,...
你 发表评论:
欢迎- 一周热门
-
-
Linux:Ubuntu22.04上安装python3.11,简单易上手
-
宝马阿布达比分公司推出独特M4升级套件,整套升级约在20万
-
MATLAB中图片保存的五种方法(一)(matlab中保存图片命令)
-
别再傻傻搞不清楚Workstation Player和Workstation Pro的区别了
-
Linux上使用tinyproxy快速搭建HTTP/HTTPS代理器
-
如何提取、修改、强刷A卡bios a卡刷bios工具
-
Element Plus 的 Dialog 组件实现点击遮罩层不关闭对话框
-
日本组合“岚”将于2020年12月31日停止团体活动
-
SpringCloud OpenFeign 使用 okhttp 发送 HTTP 请求与 HTTP/2 探索
-
tinymce 号称富文本编辑器世界第一,大家同意么?
-
- 最近发表
-
- 5款Syslog集中系统日志常用工具对比推荐
- 跨平台、多数据库支持的开源数据库管理工具——DBeaver
- 强烈推荐!数据库管理工具:Navicat Premium 16.3.2 (64位)
- 3 年 Java 程序员还玩不转 MongoDB,网友:失望
- 拯救MongoDB管理员的GUI工具大赏:从菜鸟到极客的生存指南
- mongodb/redis/neo4j 如何自己打造一个 web 数据库可视化客户端?
- solidworks使用心得,纯干货!建议大家收藏
- 统一规约-关乎数字化的真正实现(规范统一性)
- Elasticsearch节点角色配置详解(Node)
- 产前母婴用品分享 篇一:我的母婴购物清单及单品推荐
- 标签列表
-
- dialog.js (57)
- importnew (44)
- windows93网页版 (44)
- yii2框架的优缺点 (45)
- tinyeditor (45)
- qt5.5 (60)
- windowsserver2016镜像下载 (52)
- okhttputils (51)
- android-gif-drawable (53)
- 时间轴插件 (56)
- docker systemd (65)
- slider.js (47)
- android webview缓存 (46)
- pagination.js (59)
- loadjs (62)
- openssl1.0.2 (48)
- velocity模板引擎 (48)
- pcre library (47)
- zabbix微信报警脚本 (63)
- jnetpcap (49)
- pdfrenderer (43)
- fastutil (48)
- uinavigationcontroller (53)
- bitbucket.org (44)
- python websocket-client (47)