分布式文件系统MinIO¶
对象存储的方式对比¶
| 存储方式 | 优点 | 缺点 |
|---|---|---|
| 服务器磁盘 | 开发便捷,成本低 | 扩展困难 |
| 分布式文件系统 | 容易实现扩容 | 复杂度高 |
| 第三方存储 | 开发简单,功能强大,免维护 | 收费 |
分布式文件系统¶
| 存储方式 | 优点 | 缺点 |
|---|---|---|
| FastDFS | 1,主备服务,高可用 2,支持主从文件,支持自定义扩展名 3,支持动态扩容 |
1,没有完备官方文档,近几年没有更新 2,环境搭建较为麻烦 |
| MinIO | 1,性能高,准硬件条件下它能达到55GB/s的读、35GB/s的写速率 2,部署自带管理界面 3,MinIO.Inc运营的开源项目,社区活跃度高 4,提供了所有主流开发语言的SDK |
1,不支持动态增加节点 |
对象存储服务MinIO¶
基于Apache License v2.0开源协议的对象存储服务。
采用Golang实现,服务端可以工作在Windows、Linux、OS X和FreeBSD上。
配置简单,基本是复制可执行程序,单行命令可以运行起来。
MinIO兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
S3 ( Simple Storage Service简单存储服务)
基本概念
- bucket – 类比于文件系统的目录
- Object – 类比文件系统的文件
- Keys – 类比文件名
官网文档:http://docs.minio.org.cn/docs/
MinIO特点¶
- 数据保护
Minio使用Minio Erasure Code(纠删码)来防止硬件故障。即便损坏一半以上的driver,但是仍然可以从中恢复。
- 高性能
在标准硬件条件下它能达到55GB/s的读、35GB/s的写速率
- 可扩容
不同MinIO集群可以组成联邦,并形成一个全局的命名空间,并跨越多个数据中心
- SDK支持
它得到类似Java、Python或Go等语言的sdk支持
- 有操作页面
面向用户友好的简单操作界面,非常方便的管理Bucket及里面的文件资源
- 功能简单
这一设计原则让MinIO不容易出错、更快启动
- 丰富的API
支持文件资源的分享连接及分享链接的过期策略、存储桶操作、文件列表访问及文件上传下载的基本功能等。
- 文件变化主动通知
存储桶(Bucket)如果发生改变,比如上传对象和删除对象,可以使用存储桶事件通知机制进行监控,并通过以下方式发布出去:AMQP、MQTT、Elasticsearch、Redis、NATS、MySQL、Kafka、Webhooks等。
Docker安装MinIO¶
1、拉取镜像
docker pull quay.io/minio/minio
2、创建持久化存储目录
mkdir -p ~/minio/data
3、运行容器
docker run -d \
--restart=always \
-p 9000:9000 \
-p 9001:9001 \
--name minio \
-v ~/minio/data:/data \
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=your_strong_password" \
quay.io/minio/minio server /data --console-address ":9001"
参数说明:
-p 9000:9000:API访问端口(S3协议)-p 9001:9001:Web控制台端口-v ~/minio/data:/data:挂载数据目录(本地目录:容器目录)MINIO_ROOT_USER:管理员账号(默认admin)MINIO_ROOT_PASSWORD:管理员密码(至少8字符)--console-address ":9001":强制启用Web控制台
4、验证安装
- 查看容器状态:
docker ps | grep minio
- 访问Web控制台:
打开浏览器访问:http://服务器IP:9001
用户名密码见上面的docker容器配置
封装MinIO为starter¶
项目里面有各种微服务,如果MinIO在每一个微服务下都去集成的话,非常麻烦,所以抽出来文件服务-starter。
1、创建模块heima-file-starter¶
导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.17</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
2、配置类¶
MinIOConfigProperties.java
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.io.Serializable;
@Data
@ConfigurationProperties(prefix = "minio") // 文件上传 配置前缀minio
public class MinIOConfigProperties implements Serializable {
private String accessKey;
private String secretKey;
private String bucket;
private String endpoint;
private String readPath;
}
MinIOConfig.java
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MinIO 自动配置类
* - 仅在 classpath 中存在 MinioClient 并且配置了 minio.endpoint 时生效
* - 负责创建 MinioClient Bean 并绑定 MinIOConfigProperties
*/
@Data
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({MinIOConfigProperties.class})
@ConditionalOnClass(MinioClient.class)
@ConditionalOnProperty(prefix = "minio", name = "endpoint")
public class MinIOConfig {
private final MinIOConfigProperties minIOConfigProperties;
public MinIOConfig(MinIOConfigProperties minIOConfigProperties) {
this.minIOConfigProperties = minIOConfigProperties;
}
@Bean
public MinioClient buildMinioClient() {
return MinioClient
.builder()
.credentials(minIOConfigProperties.getAccessKey(), minIOConfigProperties.getSecretKey())
.endpoint(minIOConfigProperties.getEndpoint())
.build();
}
}
3、封装操作minIO类¶
FileStorageService
import java.io.InputStream;
public interface FileStorageService {
/**
* 上传图片文件
*
* @param prefix 文件前缀
* @param filename 文件名
* @param inputStream 文件流
* @return 文件全路径
*/
String uploadImgFile(String prefix, String filename, InputStream inputStream);
/**
* 上传html文件
*
* @param prefix 文件前缀
* @param filename 文件名
* @param inputStream 文件流
* @return 文件全路径
*/
String uploadHtmlFile(String prefix, String filename, InputStream inputStream);
/**
* 上传数据库文件
*
* @param prefix 文件前缀
* @param filename 文件名
* @param inputStream 文件流
* @return 文件全路径
*/
String uploadDbFile(String prefix, String filename, InputStream inputStream);
/**
* 删除文件
*
* @param pathUrl 文件全路径
*/
void delete(String pathUrl);
/**
* 下载文件
*
* @param pathUrl 文件全路径
* @return
*/
byte[] downLoadFile(String pathUrl);
}
MinIOFileStorageService
import com.hhjava.www.config.MinIOConfigProperties;
import com.hhjava.www.service.FileStorageService;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@Service
@Slf4j
//@EnableConfigurationProperties(MinIOConfigProperties.class)
//@Import(MinIOConfig.class)
public class MinIOFileStorageService implements FileStorageService {
@Autowired
private MinioClient minioClient;
@Autowired
private MinIOConfigProperties minIOConfigProperties;
private final static String SEPARATOR = "/";
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd");
/**
* 构建文件路径
*
* @param dirPath 目录路径
* @param filename 文件名
* @return 完整文件路径
*/
private String buildFilePath(String dirPath, String filename) {
StringBuilder stringBuilder = new StringBuilder(50);
if (StringUtils.hasText(dirPath)) {
stringBuilder.append(dirPath).append(SEPARATOR);
}
String todayStr = LocalDate.now().format(DATE_FORMATTER);
stringBuilder.append(todayStr).append(SEPARATOR).append(filename);
return stringBuilder.toString();
}
/**
* 通用文件上传方法
*
* @param prefix 文件前缀
* @param filename 文件名
* @param inputStream 文件流
* @param contentType 文件类型
* @return 文件全路径
*/
private String uploadFile(String prefix, String filename, InputStream inputStream, String contentType) {
String filePath = buildFilePath(prefix, filename);
try {
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.object(filePath)
.contentType(contentType)
.bucket(minIOConfigProperties.getBucket())
.stream(inputStream, inputStream.available(), -1) // 文件流
.build();
minioClient.putObject(putObjectArgs);
return minIOConfigProperties.getReadPath() + SEPARATOR + minIOConfigProperties.getBucket() +
SEPARATOR + filePath;
} catch (Exception ex) {
log.error("MinIO 文件上传失败,文件路径: {}, 错误信息: {}", filePath, ex.getMessage(), ex);
throw new RuntimeException("上传文件失败");
}
}
/**
* 上传图片文件
*
* @param prefix 文件前缀
* @param filename 文件名
* @param inputStream 文件流
* @return 文件全路径
*/
@Override
public String uploadImgFile(String prefix, String filename, InputStream inputStream) {
return uploadFile(prefix, filename, inputStream, "image/*");
}
/**
* 上传html文件
*
* @param prefix 文件前缀
* @param filename 文件名
* @param inputStream 文件流
* @return 文件全路径
*/
@Override
public String uploadHtmlFile(String prefix, String filename, InputStream inputStream) {
return uploadFile(prefix, filename, inputStream, "text/html");
}
/**
* 上传数据库文件
*
* @param prefix 文件前缀
* @param filename 文件名
* @param inputStream 文件流
* @return 文件全路径
*/
@Override
public String uploadDbFile(String prefix, String filename, InputStream inputStream) {
return uploadFile(prefix, filename, inputStream, "application/x-sqlite3");
}
/**
* 删除文件
*
* @param pathUrl 文件全路径
*/
@Override
public void delete(String pathUrl) {
String key = pathUrl.replace(minIOConfigProperties.getEndpoint() + SEPARATOR, "");
int index = key.indexOf(SEPARATOR);
String bucket = key.substring(0, index);
String filePath = key.substring(index + 1);
// 删除Objects
RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucket).object(filePath).build();
try {
minioClient.removeObject(removeObjectArgs);
log.info("文件删除成功,路径: {}", pathUrl);
} catch (Exception e) {
log.error("MinIO 文件删除失败,路径: {}, 错误信息: {}", pathUrl, e.getMessage(), e);
}
}
/**
* 下载文件
*
* @param pathUrl 文件全路径
* @return 文件流
*/
@Override
public byte[] downLoadFile(String pathUrl) {
String key = pathUrl.replace(minIOConfigProperties.getEndpoint() + SEPARATOR, "");
int index = key.indexOf(SEPARATOR);
String bucket = key.substring(0, index);
String filePath = key.substring(index + 1);
try (InputStream inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(filePath).build());
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
byte[] buff = new byte[4096]; // 缓冲区大小
int rc;
while ((rc = inputStream.read(buff)) > 0) {
byteArrayOutputStream.write(buff, 0, rc);
}
log.info("文件下载成功,路径: {}", pathUrl);
return byteArrayOutputStream.toByteArray();
} catch (Exception e) {
log.error("MinIO 文件下载失败,路径: {}, 错误信息: {}", pathUrl, e.getMessage(), e);
throw new RuntimeException("下载文件失败", e);
}
}
}
4、对外加入自动配置¶
在 src/main/resources/META-INF/spring/ 下新增文件org.springframework.boot.autoconfigure.AutoConfiguration.imports,列出自动配置类全名。
com.hhjava.www.config.MinIOConfig
5、其他微服务使用¶
1、pom.xml文件中导入heima-file-starter的依赖
<dependency>
<groupId>com.heima</groupId>
<artifactId>heima-file-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2、在微服务application.yml中添加minio所需要的配置
注:也可以在nacos中去配置下面内容。
minio:
accessKey: admin
secretKey: your_strong_password
bucket: backup
endpoint: http://47.120.67.123:9000
readPath: http://47.120.67.123:9001/ # 文件访问域名
3、在对应使用的业务类中注入FileStorageService
import com.heima.file.service.FileStorageService;
import com.heima.minio.MinioApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@SpringBootTest(classes = MinioApplication.class)
@RunWith(SpringRunner.class)
public class MinioTest {
@Autowired
private FileStorageService fileStorageService;
@Test
public void testUpdateImgFile() {
try {
FileInputStream fileInputStream = new FileInputStream("E:\\tmp\\ak47.jpg");
String filePath = fileStorageService.uploadImgFile("", "ak47.jpg", fileInputStream);
System.out.println(filePath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}