文章目录
- 一、分布式文件系统应用场景
- 1. Minio介绍
- Minio优点
- 2. MinIO的基础概念、
- 3. 纠删码ES(Erasure Code)
- 4. 存储形式
- 5. 存储方案
- 二、Docker部署单机Minio
- 三、minio纠删码模式部署
- 四、分布式集群部署
- 分布式存储可靠性常用方法
- 冗余
- 校验
- 分布式Minio优势
- 运行分布式minio
- 使用docker compose部署minio
- 五、Minio客户端使用
- docker 中使用MC
- 六、Minio整合SpringBoot
- 1. 编写MinioConfig类
- 2. 编写application.yml
- 3. 编写Controller测试类(查询、下载、上传)
一、分布式文件系统应用场景
互联网海量非结构化数据的存储需求。电商图片、视频、网盘文件、社交图片。
分布式文件存储系统Minio与FastDFS的区别。FastDFS文件名是自动生成的。
1. Minio介绍
MinIO是一个开源的对象存储服务。兼容亚马逊S3云存储服务接口。适合存储大量非结构化数据,eg:图片、视频、日志文件、备份数据、容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几K到最大5T不等。
MinIO是一个非常轻量的服务,可以很简单的和其他应用结合,类似NodeJS、Redis或者MySQL。
中文官方网站:http://minio.org.cn/
Minio优点
- 部署简单:一个single二进制文件即是一切,还可支持各种平台
- minio支持海量存储,可按zone扩展,支持单个对象最大5TB
- 兼容Amazon S3,充分考虑开发人员的需求和体验
- 低冗余且磁盘损坏高容忍,标准且最高的数据荣誉系数为2(即存储一个1M的数据,实际占用2M),但在任意n/2块disk损坏的情况下依赖可以读出数据(n为一个纠删码集合中的disk数量)。并且这种损坏恢复是基于单个对象的,而不是基于整个存储卷的。
- 读写性能优异
2. MinIO的基础概念、
- Object: 存储到MinIO的基本对象,如文件、字节流等
- Bucket: 用来存储Object的逻辑空间。每个Bucket之间的数据是相互隔离的。对于客户端来说,相当于一个存放文件的顶层文件夹。(隔离的作用)
- Drive: 即存储数据的磁盘,在Minio启动时,以参数的方式传入。Minio中所有的对象数据都会存储在Drive中。
- Set: 即一组Drive的集合,分布式部署根据集群规模自动划分一个或多个Set,每个Set中的Dirve分布位置不同。一个对象存储在一个Set上。
- 一个对象存储在一个Set上
- 一个集群划分为多个Set
- 一个Set包含的Drive数量固定,默认由系统根据集群规模计算
- 一个Set中的Drive尽可能分布在不同的节点上。
3. 纠删码ES(Erasure Code)
MinIO使用纠删码机制来保证高可用,使用HighWayHash来处理数据损坏。关于纠删码,简单的说即通过计算,将丢失的数据还原,可以将n份原始数据,增加m份数据。并能通过n+m份中的任意n份数据,还原为原始数据。
即如果有任意小于m份的数据失败,仍可以通过剩下的数据还原。
4. 存储形式
文件对象上传到Minio,会在对应的数据磁盘中,以Bucket名称为目录,文件名为下一级目录,文件名下为part.1和xl.meta,前者为编码数据块及校验块,后者是元数据文件。
5. 存储方案
二、Docker部署单机Minio
- 参考官方文档:https://min.io/docs/minio/container/index.html
- 命令启动
这里我们用的是docker,所以需要将命令换成docker run
λ docker search minio
λ docker pull minio/minio
docker run -p 9000:9000 -p 9090:9090 --name minio -v D:\docker\minio\data:/data -e "MINIO_ROOT_USER=admin" -e "MINIO_ROOT_PASSWORD=admin123" minio/minio server /data --console-address ":9090"
# 下面是多行模式的
docker run -d \ # 后台启动
-p 9000:9000 \ # api
-p 9090:9090 \ # 访问地址
--name minio
-v D:\docker\minio\data:/data \ #映射数据文件
-e "MINIO_ROOT_USER=admin" \ #账号
-e "MINIO_ROOT_PASSWORD=admin123" \ #密码
minio/minio server /data --console-address ":9090" #指定访问端口9090不变更
启动控制台打印内容:
-
访问:http://localhost:9090/login 账号:admin,密码:admin123
-
创建存储桶,并上传文件测试,查看映射磁盘目录的变化。
这里我参考的版本是说单台没有启用纠删码存储的即为源文件,而我这里docker启动的却存储的是λ tree D:\docker\minio\data └─test1 └─Doc1.docx └─f9014ffb-63fd-4f5d-a250-fbccf131f87d
三、minio纠删码模式部署
minio使用纠删码(erasure code)与校验和(checksum)来保护数据不受硬件故障和无声数据损坏。即使丢失一半(n/2)数据的硬盘,仍然可以恢复数据。当有一半的磁盘损坏时数据无法上传,必须存在2n+1块才可以。
- docker 部署
docker run -p 9000:9000 -p 9090:9090 --name minio -v D:\docker\minio\data1:/data1 -v D:\docker\minio\data2:/data2 -v D:\docker\minio\data3:/data3 -v D:\docker\minio\data4:/data4 -v D:\docker\minio\data5:/data5 -v D:\docker\minio\data6:/data6 -v D:\docker\minio\data7:/data7 -v D:\docker\minio\data8:/data8 -e "MINIO_ROOT_USER=admin" -e "MINIO_ROOT_PASSWORD=admin123" minio/minio server /data{1...8} --console-address ":9090"
docker run -d \ # 后台启动
-p 9000:9000 \ # api
-p 9091:9091 \ # 访问地址
--name minio
-v D:\docker\minio\data1:/data1 \
-v D:\docker\minio\data2:/data2 \
-v D:\docker\minio\data3:/data3 \
-v D:\docker\minio\data4:/data4 \
-v D:\docker\minio\data5:/data5 \
-v D:\docker\minio\data6:/data6 \
-v D:\docker\minio\data7:/data7 \
-v D:\docker\minio\data8:/data8 \
-e "MINIO_ROOT_USER=admin" \ #账号
-e "MINIO_ROOT_PASSWORD=admin123" \ #密码
minio/minio server /data{1...8} --console-address ":9091" #指定访问端口9090不变更
四、分布式集群部署
分布式minio可以将多块硬盘组成一个对象存储服务。由于硬盘分布在不同的节点上,分布式minio避免了单点故障。
分布式存储可靠性常用方法
分布式存储,关键点在于数据可靠性、完整性、不丢失损坏。只有在可靠的前提下才追求一致性、高可用、高性能。
冗余
冗余即对存储的数据进行备份,当数据丢失、损坏,可以使用副本进行恢复。而备份份数决定了可靠性。可靠性是允许丢失其中一部分数据。当前分布式系统中采用该模式的,eg:Hadoop的文件系统(3个副本)、Redis集群、Mysql主备。
校验
校验即通过校验码的计算方式,对丢失损坏的数据进行校验、还原。注意:这里有两个作用,一个校验通过对数据进行校验和计算,可以检查数据的完整性,检查数据是否损坏、更改,在数据传输中使用。二是恢复还原,通过对数据结合校验码计算,还原丢失损坏的数据,在保证可靠性的前提下减少冗余。eg:单机硬盘存储的RAID技术、纠删码技术。
分布式Minio优势
- 数据保护:分布式minio采用纠删码来防范多个节点宕机和位衰减(bit rot)。分布式minio最少需要4个磁盘,使用分布式minio自动引入纠删码。
- 高可用:单机minio存在单点故障。而如果一个有N块硬盘的分布式minio,只要有n/2块硬盘可用,则数据安全可读。可写需要n/2+1。
- 一致性:minio在分布式和单机模式下,所有写操作都严格遵守read-after-write一致性。
运行分布式minio
启动一个分布式minio实例,只需要把硬盘位置作为参数传给minio server命令即可,然后在其他节点运行相同命令。
- 分布式minio所有节点需要同样的access密钥和secret密钥以保证所有节点可以建立连接。新版(MINIO_ROOT_USER、MINIO_ROOT_PASSWORD)
- 分布式 minio使用磁盘必须干净无数据。
- 分布式minio节点时间差不得超过3秒,可以使用NTP来保证时间同步。
使用docker compose部署minio
version: '3.7'
# Settings and configurations that are common for all containers
x-minio-common: &minio-common
image: minio/minio:latest
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
expose:
- "9000"
- "9001"
environment:
MINIO_ROOT_USER: minio
MINIO_ROOT_PASSWORD: minio123
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
# starts 4 docker containers running minio server instances.
# using nginx reverse proxy, load balancing, you can access
# it through port 9000.
services:
minio1:
<<: *minio-common
hostname: minio1
volumes:
- data1-1:/data1
- data1-2:/data2
minio2:
<<: *minio-common
hostname: minio2
volumes:
- data2-1:/data1
- data2-2:/data2
minio3:
<<: *minio-common
hostname: minio3
volumes:
- data3-1:/data1
- data3-2:/data2
minio4:
<<: *minio-common
hostname: minio4
volumes:
- data4-1:/data1
- data4-2:/data2
nginx:
image: nginx:1.19.2-alpine
hostname: nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
ports:
- "9000:9000"
- "9001:9001"
depends_on:
- minio1
- minio2
- minio3
- minio4
## By default this config uses default local driver,
## For custom volumes replace with volume driver configuration.
volumes:
data1-1:
data1-2:
data2-1:
data2-2:
data3-1:
data3-2:
data4-1:
data4-2:
五、Minio客户端使用
MinIO Client(MC)为ls、cat、cp、mirror、diff、find等unix命令提供了一种替代方案。
ls # 列出文件和文件夹。
mb # 创建一个存储桶或一个文件夹。
cat # 显示文件和对象内容。
pipe # 将一个STDIN重定向到一个对象或者文件或者STDOUT。
share # 生成用于共享的URL。
cp # 拷贝文件和对象。
mirror # 给存储桶和文件夹做镜像。
find # 基于参数查找文件。
diff # 对两个文件夹或者存储桶比较差异。
rm # 删除文件和对象。
events # 管理对象通知。
watch # 监视文件和对象的事件
policy # 管理访问策略。
config # 管理mc配置文件。
update # 检查软件更新。
version # 输出版本信息。
http://docs.minio.org.cn/minio/baremetal/reference/minio-cli/minio-mc.html
docker 中使用MC
启动控制台
λ docker pull minio/mc
λ docker run -it --name mc --entrypoint=/bin/sh minio/mc
λ docker start -i mc
配置管理
# 查询mc host配置
mc config host ls
# 添加minio服务
mc config host add minio-server http://172.17.0.3:9090 admin admin123
# 删除host
mc config host remove minio-server
文件管理
# 上传下载
mc cp 源文件地址 目标地址
mc rm 文件地址
Bucket管理
# 创建桶
mc mb hostname / bucketName
eg:mc mb minio-server /bucket01
# 删除桶 --force表示强制删除
mc rb hostname /bucketName
eg:mc rb --force minio-server /bucket02
admin管理
service # 服务重启并停止所有MinIO服务器
update # 更新所有MinIO服务器
info # 显示MinIO服务器信息
user # 管理用户
group # 管理小组
policy # MinIO服务器中定义的策略管理
config # MinIO服务器配置管理
heal # 修复MinIO服务器上的磁盘,存储桶和对象
profile # 生成概要文件数据以进行调试
top # 提供MinIo的顶部统计信息
trace # 跟踪显示MinIO服务器的http跟踪
console # 控制台显示MinIo服务器的控制台日志
prometheus # Prometheus管理Prometheus配置
kms # kms执行KMS管理操作
六、Minio整合SpringBoot
1. 编写MinioConfig类
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {
private String url;
private String username;
private String pwd;
@Bean
public MinioClient getMinioClient() {
MinioClient minioClient = MinioClient.builder().endpoint(this.getUrl())
.credentials(this.getUsername(), this.getPwd())
.build();
return minioClient;
}
}
2. 编写application.yml
minio:
url: http://localhost:9000
username: admin
pwd: admin123
bucket: test
server:
port: 8888
spring:
servlet:
multipart:
max-file-size: 100MB
max-request-size: 500MB
3. 编写Controller测试类(查询、下载、上传)
@RestController
@Slf4j
public class MinioController {
@Autowired
private MinioClient minioClient;
@Value("${minio.bucket}")
private String bucketName;
@GetMapping("/list")
public List<Object> list() throws Exception {
Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
.bucket(bucketName).build());
ArrayList<Object> list = new ArrayList<>();
Iterator<Result<Item>> iterator = results.iterator();
while (iterator.hasNext()) {
Item item = iterator.next().get();
list.add(JSONUtil.parse(this.format(item.objectName(), item.size())));
}
return list;
}
@GetMapping("/download/{fileName}")
public void download(HttpServletResponse resp, @PathVariable("fileName") String fileName) throws Exception {
StatObjectResponse stat = minioClient.statObject(StatObjectArgs.builder()
.bucket(bucketName).object(fileName).build());
resp.setContentType(stat.contentType());
resp.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
GetObjectResponse object = minioClient.getObject(GetObjectArgs.builder()
.bucket(bucketName).object(fileName).build());
IOUtils.copy(object, resp.getOutputStream());
}
@GetMapping("/delete/{fileName}")
public String delete(@PathVariable("fileName") String fileName) {
try {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
} catch (Exception e) {
e.printStackTrace();
return "删除失败!" + e.getMessage();
}
return "删除成功!";
}
@GetMapping("/upload")
public String upload(MultipartFile[] files) throws IOException {
if (files == null || files.length == 0) {
return "上传文件不能为空!";
}
InputStream ins = null;
try {
ArrayList<String> fileNames = new ArrayList<>(files.length);
for (MultipartFile file : files) {
String filename = file.getOriginalFilename();
fileNames.add(filename);
ins = file.getInputStream();
ObjectWriteResponse response = minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketName).object(filename)
.stream(ins, file.getSize(), -1)
.contentType(file.getContentType())
.build());
}
} catch (Exception e) {
e.printStackTrace();
return "上传失败!" + e.getMessage();
} finally {
if (ins != null) {
ins.close();
}
}
return "上传成功!";
}
private String format(String objectName, long size) {
return String.format("{'fileName':'%s','fileSize':'%d'}", objectName, size);
}
}