MinIO是一个高性能的开源对象存储服务器,与Amazon S3兼容。它使用Go语言编写,可以在多种操作系统上运行,如Linux、MacOS和Windows等。MinIO的分布式特性使其能够轻松扩展存储容量和处理能力,满足大规模数据存储的需求。
使用Docker搭建一个支持跨域访问的文件服务器可以有多种方式,具体选择哪一种取决于你的需求、资源以及对不同技术栈的熟悉程度。以下是几种推荐的方式:
-
Nginx Nginx 是一个高性能的HTTP和反向代理服务器,可以很容易地配置为静态文件服务器,同时添加CORS(Cross-Origin Resource Sharing)头以支持跨域访问。你可以使用官方的Nginx Docker镜像,并编写一个Nginx配置文件来实现这一目的。
-
Apache Tomcat 如果你已经熟悉Java生态系统,Tomcat也是一个可行的选择。Tomcat可以作为静态文件服务器,通过配置其
web.xml
和server.xml
文件,你能够添加必要的CORS支持。但是,Nginx通常比Tomcat更轻量级,更适合静态文件服务。 -
FileBrowser FileBrowser是一个易于使用的文件管理系统,可以部署在Docker容器中。它提供了文件浏览、上传、下载和分享的功能,并且内置了CORS支持,非常适合管理大量的媒体文件如视频和图片。
-
FTP服务器 如果你想要一个传统的文件传输协议服务器,可以考虑使用Docker部署像vsftpd这样的FTP服务器。然而,FTP服务器默认不支持跨域访问,你可能需要额外的配置或中间件(如Nginx或Apache)来实现CORS。
-
S3-Compatible Storage 使用像MinIO这样的S3兼容存储系统,它可以很容易地与Docker集成,提供高性能的对象存储,同时也支持CORS配置。
为了实现跨域访问,你需要确保你的服务器响应中包含正确的CORS头部。例如,在Nginx配置中,你可以添加如下指令:
Nginx
深色版本
1location ~* \.(jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|wav|mp3|aac)$ {
2 add_header Access-Control-Allow-Origin *;
3}
如果你需要的是一个简单且成熟的解决方案,那么使用Nginx或FileBrowser会是比较好的选择。Nginx在性能和灵活性上非常出色,而FileBrowser则提供了用户友好的界面和管理功能。如果你只需要基本的文件服务,Nginx可能是更轻便的选择;如果需要更丰富的文件管理功能,那么FileBrowser会是更好的选择。
参考:使用Minio搭建文件服务器_开源的文件服务器-CSDN博客
参考:Deploy MinIO: Single-Node Single-Drive — MinIO Object Storage for Linux
参考:MinIO系列:从零开始搭建高效文件存储服务-百度开发者中心 (baidu.com)
参考:Minio搭建文件服务器_minio服务器搭建-CSDN博客
参考:分布式对象存储minio从0到高手的保姆级教程 - 知乎 (zhihu.com)
Minio是一个开源的对象存储服务器,可以用来存储和管理大规模的数据。我们可以用它来存储图片或者视频资源。
minio和阿里云(腾讯云、百度云等)oss对比
托管方式:MinIO 是一个开源的对象存储系统,可以在自己的服务器或云环境中进行部署和管理。而阿里云 OSS 是由阿里云提供的托管服务,不需要自己购买服务器或进行部署。
成本:MinIO 是一个开源项目,可以免费使用,您只需要支付服务器和网络等基础设施的费用。而阿里云 OSS 是一个收费的云服务,费用根据存储容量、数据传输和访问次数等因素而定。
功能和性能:阿里云 OSS 是一个经过大规模使用和测试的云存储服务,提供了丰富的功能和高可用性。MinIO 也提供了类似的功能,并专注于提供高性能和低延迟的对象存储服务。
数据保护和备份:阿里云 OSS 提供了多层次的数据冗余和备份机制,以确保数据的安全性和可用性。MinIO 也提供了类似的数据冗余和备份机制,但需要用户自行配置和管理。
地理位置和可用区:阿里云 OSS 在全球范围内提供了多个地理位置和可用区来存储数据,以满足用户的需求。MinIO 则需要用户自行设置多个节点并进行数据的分布式存储。
总结:如果想要免费但折腾的话,可以考虑minio;如果不介意收费的话,肯定选择专业且服务周到的云服务。(当然,minio也提供商业技术服务)。
注:Minio 完全兼容AWS S3协议。这意味着你可以使用Minio API来访问和操作AWS S3服务;同时,你也可以使用S3API来访问Minio服务器。
Minio优点
- 部署简单: 一个single二进制文件即是一切,还可支持各种平台。
- minio支持海量存储,可按zone扩展(原zone不受任何影响),支持单个对象最大5TB;
- 兼容Amazon S3接口,充分考虑开发人员的需求和体验;
- 低冗余且磁盘损坏高容忍,标准且最高的数据冗余系数为2(即存储一个1M的数据对象,实际占用磁盘空间为2M)。但在任意n/2块disk损坏的情况下依然可以读出数据(n为一个纠删码集合(Erasure Coding Set)中的disk数量)。并且这种损坏恢复是基于单个对象的,而不是基于整个存储卷的。
- 读写性能优异
1.2 MinIO的基础概念
- Object:存储到 Minio 的基本对象,如文件、字节流,Anything...
- Bucket:用来存储 Object 的逻辑空间。每个 Bucket 之间的数据是相互隔离的。对于客户端而言,就相当于一个存放文件的顶层文件夹。
- 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尽可能分布在不同的节点上
1.3 纠删码EC(Erasure Code)
MinIO 使用纠删码机制来保证高可靠性,使用 highwayhash 来处理数据损坏( Bit Rot Protection )。
关于纠删码,简单来说就是可以通过数学计算,把丢失的数据进行还原,它可以将n份原始数据,增加m份数据,并能通过n+m份中的任意n份数据,还原为原始数据。即如果有任意小于等于m份的数据失效,仍然能通过剩下的数据还原出来。
1.4 存储形式
文件对象上传到 MinIO ,会在对应的数据存储磁盘中,以 Bucket 名称为目录,文件名称为下一级目录,文件名下是 part.1 和 xl.meta(老版本,最新版本如下图),前者是编码数据块及检验块,后者是元数据文件。
linux 在指定目录下执行 tree 命令(yum install tree 先安装下)
1.5 存储方案
2.0 官方推荐
磁盘文件格式
官方文档推荐磁盘文件格式使用 xfs
多磁盘
磁盘需要保持一致
- 文件格式一致
- 容量一致
- 每个区使用偶数数量的磁盘,便于纠删码模式
- 每个区的磁盘数量需要一致,所以扩展后新的区和原始区的磁盘的数量、容量、格式都是一致的
参考:扩展一个分布式MinIO部署 — MinIO中文文档 | MinIO Linux中文文档
2.1 单机部署
minio server的standalone模式,即要管理的磁盘都在host本地。该启动模式一般仅用于实验环境、测试环境的验证和学习使用。在standalone模式下,还可以分为non-erasure code mode和erasure code mode。
non-erasure code mode
在此启动模式下,对于每一份对象数据,minio直接在data下面存储这份数据,不会建立副本,也不会启用纠删码机制。因此,这种模式无论是服务实例还是磁盘都是“单点”,无任何高可用保障,磁盘损坏就表示数据丢失。
erasure code mode
此模式为minio server实例传入多个本地磁盘参数。一旦遇到多于一个磁盘参数,minio server会自动启用erasure code mode。erasure code对磁盘的个数是有要求的,如不满足要求,实例启动将失败。 erasure code启用后,要求传给minio server的endpoint(standalone模式下,即本地磁盘上的目录)至少为4个。
基于 centos
参考docker的安装方式
单节点多硬盘部署MinIO — MinIO中文文档 | MinIO Container中文文档
Linux下使用Docker安装
//拉取镜像
docker pull minio/minio 下载不了
docker pull quay.io/minio/minio 可以下载
// 创建数据存储目录
mkdir home/minio-data
// 创建minio
docker run \
--name minio \
-p 9000:9000 \
-p 9090:9090 \
-d \
-e "MINIO_ROOT_USER=minio" \
-e "MINIO_ROOT_PASSWORD=minio123" \
-v /home/minio-data:/data \
-v /usr/local/minio-config:/root/.minio \
minio/minio server /data --console-address ":9090" --address ":9000"
docker run --name minio -p 9000:9000 -p 9090:9090 -d -e "MINIO_ROOT_USER=minio" -e "MINIO_ROOT_PASSWORD=minio123" -v /home/minio-data:/data -v /usr/local/minio-config:/root/.minio minio/minio server /data --console-address ":9090" --address ":9000"
下面的待测试 多磁盘的
docker run -dt \
-p 9000:9000 -p 9001:9001 \
-v PATH1:/data-1 \
-v PATH2:/data-2 \
-v PATH3:/data-3 \
-v PATH4:/data-4 \
-v /etc/default/minio:/etc/config.env \
-e "MINIO_CONFIG_ENV_FILE=/etc/config.env" \
--name "minio_local" \
minio server --console-address ":9001"
基于docker-compose 的 分布式对象存储minio从0到高手的保姆级教程 - 知乎 (zhihu.com)
参考:Minio搭建文件服务器_minio服务器搭建-CSDN博客
MinIO Consolehttp://192.168.1.247:9090/login
其初始用户名为minio,初始密码为minio123
访问服务器9090端口
此处访问控制台的端口为9090,后面文件上传用的端口是9000
1.3 Minio入门
Java相关 Java Quickstart Guide — MinIO Object Storage for Linux
具体步骤:
1、创建Bucket
1、依赖包
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.11</version>
</dependency>
<!--hutool工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.20</version>
</dependency>
2、配置文件
3、MinioConfiguration
@Configuration
public class MinioConfiguration {
@Value("${minio.config.endpointUrl}")
private String endpointUrl;
@Value("${minio.config.accessKey}")
private String accessKey;
@Value("${minio.config.secretKey}")
private String secretKey;
@Value("${minio.config.bucketName}")
private String bucketName;
@Bean
@Scope("singleton")
public MinioClient minioClient() { // 方法名应该更具描述性,比如 minioClient
MinioClient minioClient = MinioClient.builder()
.endpoint(endpointUrl)
.credentials(accessKey, secretKey)
.build();
return minioClient;
}
}
4、MinioService
@Service
@Slf4j
public class MinioService {
@Autowired
private MinioClient minioClient;
@Value("${minio.config.bucketName}")
private String bucketName; // 这里注入 bucketName
@Value("${minio.config.endpointUrl}")
private String endpointUrl;
/**
* 上传文件
* @param multipartFile
* @return
*/
public String fileUpload(MultipartFile multipartFile, String contentType) {
try {
String dateDir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
String uuid = UUID.randomUUID().toString().replace("-", "");
String fileName = dateDir + "/" + uuid + multipartFile.getOriginalFilename();
boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (!found) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
try (InputStream inputStream = multipartFile.getInputStream()) {
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.stream(inputStream, multipartFile.getSize(), -1)
.contentType(contentType)
.build();
minioClient.putObject(putObjectArgs);
log.info("File uploaded successfully to MinIO: {}", endpointUrl + "/" + bucketName + "/" + fileName);
return endpointUrl + "/" + bucketName + "/" + fileName;
}
} catch (Exception e) {
log.error("Error uploading file to MinIO", e);
throw new RuntimeException("Failed to upload file to MinIO", e);
}
}
}
5、单元测试调用
@SpringBootTest(classes = {DataChatgptApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MinioFileUploadTest {
@Autowired
private MinioService minioService;
@Test
void testFilesUploadToMinio() {
try {
Path directory = Paths.get("src/main/resources/data/video/");
Files.walk(directory)
.filter(path -> path.toFile().isFile())
.forEach(path -> uploadFileToMinio(path));
} catch (IOException e) {
e.printStackTrace();
}
}
public void uploadFileToMinio(Path filePath) {
try {
File file = filePath.toFile();
String contentType = getContentType(file);
MultipartFile multipartFile = new MockMultipartFile(
file.getName(),
file.getName(),
contentType,
new FileSystemResource(file).getInputStream());
String fileName = minioService.fileUpload(multipartFile, contentType);
System.out.println("File uploaded successfully to MinIO: " + fileName);
} catch (Exception e) {
e.printStackTrace();
}
}
private String getContentType(File file) {
String extension = getExtension(file).toLowerCase();
switch (extension) {
case "mp4":
return "video/mp4";
// 如果有其他文件类型,添加更多的 case
default:
return "application/octet-stream"; // 通用的二进制文件类型
}
}
public static String getExtension(File file) {
String fileName = file.getName();
int lastIndexOfDot = fileName.lastIndexOf('.');
if (lastIndexOfDot > 0) {
return fileName.substring(lastIndexOfDot + 1);
}
return ""; // No extension found
}
}