Docker部署MinIO分布式存储系统+SpringBoot整合MinIO
中文官网MinIO Container中文文档Minio是一个高性能、分布式对象存储系统,兼容Amazon S3 API。它可以通过搭建私有云来提供存储服务,也可以作为公共云中的对象存储服务。Minio的实现原理是基于分布式的架构,数据被分割为多个部分并存储在不同的服务器上。这些服务器通过一个一致性哈希算法将数据划分为不同的分片,并且使用Erasure Coding来提供数据冗余和容错能力。客户端
当然使用Docker之前还是需要部署docker的 点这里查看
一、什么是Minio
中文官网 MinIO Container中文文档
1.1 minio简介
Minio是一个高性能、分布式对象存储系统,兼容Amazon S3 API。它可以通过搭建私有云来提供存储服务,也可以作为公共云中的对象存储服务。
Minio的实现原理是基于分布式的架构,数据被分割为多个部分并存储在不同的服务器上。这些服务器通过一个一致性哈希算法将数据划分为不同的分片,并且使用Erasure Coding来提供数据冗余和容错能力。客户端通过S3协议与Minio进行交互,可以像操作Amazon S3一样进行数据的上传、下载、删除等操作。Minio支持水平扩展,可以根据业务需求增加或减少服务器节点。
1.2 使用场景
Minio的使用场景非常广泛,包括但不限于以下几个方面:
- 对象存储服务:作为公共云服务提供商,Minio可以提供可靠的、低延迟的对象存储服务,适用于需要大规模数据存储和访问的应用。
- 备份和归档:Minio可以作为备份和归档的存储解决方案,确保数据的安全性和可靠性。
- 多媒体存储和处理:Minio可以存储和处理大规模的多媒体文件,如图片、视频等。
- 数据湖:利用Minio提供的数据存储和分析能力,可以构建数据湖,用于存储、分析和处理海量的结构化和非结构化数据。
- 私有云存储:通过搭建私有云,可以在本地环境中提供对象存储服务,提高数据隐私和安全性。
二、部署Minio
2.1 拉取镜像
docker pull minio/minio
2.2 创建数据持久化文件夹
mkdir -p /usr/local/minio/data
2.3 启动镜像
docker run -p 9000:9000 -p 9090:9090 \
--name minio \
-d --restart=always \
-e "MINIO_ACCESS_KEY=WAkFL27XwGlOD7OA" \
-e "MINIO_SECRET_KEY=Ubn7Zuojif1kbp8ASjJff2VEs0Ko5TVt" \
-e "MINIO_ROOT_USER=WAkFL27XwGlOD7OA" \
-e "MINIO_ROOT_PASSWORD=Ubn7Zuojif1kbp8ASjJff2VEs0Ko5TVt" \
-v /usr/local/minio/data:/data \
minio/minio server \
/data --console-address ":9090" -address ":9000"
docker指令解析:
- docker run -p 9000:9000 -p 9090:9090 :
-p 9000:9000 表示将容器内的 9000 端口映射到主机的 9000 端口,用于 Minio 对象存储服务。
-p 9090:9090 表示将容器内的 9090 端口映射到主机的 9090 端口,用于 Minio 控制台。- –name minio: 将容器命名为 “minio”。
- -d --restart=always: 在后台运行容器,并在容器退出后自动重启容器。
- -e “MINIO_ACCESS_KEY=WAkFL27XwGlOD7OA”: 设置 Minio 的访问密钥,这里设置为 “WAkFL27XwGlOD7OA”。
- -e “MINIO_SECRET_KEY=Ubn7Zuojif1kbp8ASjJff2VEs0Ko5TVt”: 设置 Minio 的私有密钥,这里设置为 “Ubn7Zuojif1kbp8ASjJff2VEs0Ko5TVt”。
- -e “MINIO_ROOT_USER=WAkFL27XwGlOD7OA”: 设置 Minio 控制台的根用户,这里设置为 “WAkFL27XwGlOD7OA”。
- -e “MINIO_ROOT_PASSWORD=Ubn7Zuojif1kbp8ASjJff2VEs0Ko5TVt”: 设置 Minio 控制台的根用户密码,这里设置为 “Ubn7Zuojif1kbp8ASjJff2VEs0Ko5TVt”。
- -v /usr/local/minio/data:/data: 将主机的 “/usr/local/minio/data” 目录挂载到容器的 “/data” 目录,用于持久化存储 Minio 数据。
- minio/minio server /data --console-address “:9090” -address “:9000”: 使用 Minio 镜像创建容器,并将 “/data” 目录作为存储目录。" --console-address “:9090” -address “:9000"” 是 Minio 镜像特定的参数,指定了 Minio 控制台和对象存储服务的端口。
执行完成后接着执行 docker ps 查看当前docker运行的镜像
2.4 防火墙端口开放
//防火墙开放 连接端口
firewall-cmd --zone=public --add-port=9000/tcp --permanent
//防火墙开放 客户端界面端口
firewall-cmd --zone=public --add-port=9090/tcp --permanent
//重启防火墙 使其配置生效
systemctl restart firewalld.service
ps:如果是云服务器开了安全组的需要在安全组开放对应端口,建议只给自己的ip开放。
2.5 访问Minio
ip:9090
三、SpringBoot整合Minio
3.1 引入Maven依赖
这里使用的是 AWS S3(Amazon Simple Storage Service)的依赖,它是亚马逊提供的一种可扩展的对象存储服务。aws-s3 通用存储操作 支持所有兼容s3协议的云存储: {阿里云OSS,腾讯云COS,七牛云,京东云,minio 等。
<!--aws-s3-->
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.11.803</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-sts</artifactId>
<version>1.11.803</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-core</artifactId>
<version>1.11.803</version>
<exclusions>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</exclusion>
</exclusions>
</dependency>
3.2 配置类
application.yml 配置文件添加
#aws-s3
file:
bucketName: tallytool # 桶名称
oss:
endpoint: http://部署的服务器ip:9000
accessKey: WAkFL27XwGlOD7OA
secretKey: Ubn7Zuojif1kbp8ASjJff2VEs0Ko5TVt
配置类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* oss 配置信息
*/
@Data
@Component
@ConfigurationProperties(prefix = "file")
public class FileProperties {
/**
* 默认的存储桶名称
*/
private String bucketName = "local";
/**
* oss 文件配置信息
*/
private OssProperties oss;
}
3.3 文件抽象类
package com.itbanana.tallytool.template;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import org.springframework.beans.factory.InitializingBean;
import java.io.InputStream;
import java.util.List;
/**
* 文件操作模板
*/
public interface FileTemplate extends InitializingBean {
/**
* 创建bucket
* @param bucketName bucket名称
*/
void createBucket(String bucketName);
/**
* 获取全部bucket
* <p>
*
* API Documentation</a>
*/
List<Bucket> getAllBuckets();
/**
* @param bucketName bucket名称
* @see <a href= Documentation</a>
*/
void removeBucket(String bucketName);
/**
* 上传文件
* @param bucketName bucket名称
* @param objectName 文件名称
* @param stream 文件流
* @param contextType 文件类型
* @throws Exception
*/
void putObject(String bucketName, String objectName, InputStream stream, String contextType) throws Exception;
/**
* 上传文件
* @param bucketName bucket名称
* @param objectName 文件名称
* @param stream 文件流
* @throws Exception
*/
void putObject(String bucketName, String objectName, InputStream stream) throws Exception;
/**
* 获取文件
* @param bucketName bucket名称
* @param objectName 文件名称
* @return 二进制流 API Documentation</a>
*/
S3Object getObject(String bucketName, String objectName);
void removeObject(String bucketName, String objectName) throws Exception;
/**
* @throws Exception
*/
@Override
default void afterPropertiesSet() throws Exception {
}
/**
* 根据文件前置查询文件
* @param bucketName bucket名称
* @param prefix 前缀
* @param recursive 是否递归查询
* @return S3ObjectSummary 列表
* @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListObjects">AWS
* API Documentation</a>
*/
List<S3ObjectSummary> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive);
}
3.4 oss操作实现类
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.*;
import com.amazonaws.util.IOUtils;
import lombok.Cleanup;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
/**
* aws-s3 通用存储操作 支持所有兼容s3协议的云存储: {阿里云OSS,腾讯云COS,七牛云,京东云,minio 等}
*
*/
@Component
@RequiredArgsConstructor
public class OssTemplate implements InitializingBean, FileTemplate {
private final FileProperties properties;
private AmazonS3 amazonS3;
/**
* 创建bucket
* @param bucketName bucket名称
*/
@SneakyThrows
@Override
public void createBucket(String bucketName) {
if (!amazonS3.doesBucketExistV2(bucketName)) {
amazonS3.createBucket((bucketName));
}
}
/**
* 获取全部bucket
* <p>
*
* @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListBuckets">AWS
* API Documentation</a>
*/
@SneakyThrows
@Override
public List<Bucket> getAllBuckets() {
return amazonS3.listBuckets();
}
/**
* @param bucketName bucket名称
* @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListBuckets">AWS
* API Documentation</a>
*/
@SneakyThrows
public Optional<Bucket> getBucket(String bucketName) {
return amazonS3.listBuckets().stream().filter(b -> b.getName().equals(bucketName)).findFirst();
}
/**
* @param bucketName bucket名称
* @see <a href=
* "http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/DeleteBucket">AWS API
* Documentation</a>
*/
@SneakyThrows
@Override
public void removeBucket(String bucketName) {
amazonS3.deleteBucket(bucketName);
}
/**
* 根据文件前置查询文件
* @param bucketName bucket名称
* @param prefix 前缀
* @param recursive 是否递归查询
* @return S3ObjectSummary 列表
* @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListObjects">AWS
* API Documentation</a>
*/
@SneakyThrows
@Override
public List<S3ObjectSummary> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {
ObjectListing objectListing = amazonS3.listObjects(bucketName, prefix);
return new ArrayList<>(objectListing.getObjectSummaries());
}
/**
* 获取文件外链
* @param bucketName bucket名称
* @param objectName 文件名称
* @param expires 过期时间 <=7
* @return url
* @see AmazonS3#generatePresignedUrl(String bucketName, String key, Date expiration)
*/
@SneakyThrows
public String getObjectURL(String bucketName, String objectName, Integer expires) {
Date date = new Date();
Calendar calendar = new GregorianCalendar();
calendar.setTime(date);
calendar.add(Calendar.DAY_OF_MONTH, expires);
URL url = amazonS3.generatePresignedUrl(bucketName, objectName, calendar.getTime());
return url.toString();
}
/**
* 获取文件
* @param bucketName bucket名称
* @param objectName 文件名称
* @return 二进制流
* @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/GetObject">AWS
* API Documentation</a>
*/
@SneakyThrows
@Override
public S3Object getObject(String bucketName, String objectName) {
return amazonS3.getObject(bucketName, objectName);
}
/**
* 上传文件
* @param bucketName bucket名称
* @param objectName 文件名称
* @param stream 文件流
* @throws Exception
*/
@Override
public void putObject(String bucketName, String objectName, InputStream stream) throws Exception {
putObject(bucketName, objectName, stream, stream.available(), "application/octet-stream");
}
/**
* 上传文件
* @param bucketName bucket名称
* @param objectName 文件名称
* @param stream 文件流
* @param contextType 文件类型
* @throws Exception
*/
@Override
public void putObject(String bucketName, String objectName, InputStream stream, String contextType)
throws Exception {
putObject(bucketName, objectName, stream, stream.available(), contextType);
}
/**
* 上传文件
* @param bucketName bucket名称
* @param objectName 文件名称
* @param stream 文件流
* @param size 大小
* @param contextType 类型
* @throws Exception
* @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/PutObject">AWS
* API Documentation</a>
*/
public PutObjectResult putObject(String bucketName, String objectName, InputStream stream, long size,
String contextType) throws Exception {
// String fileName = getFileName(objectName);
byte[] bytes = IOUtils.toByteArray(stream);
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(size);
objectMetadata.setContentType(contextType);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
// 上传
return amazonS3.putObject(bucketName, objectName, byteArrayInputStream, objectMetadata);
}
/**
* 获取文件信息
* @param bucketName bucket名称
* @param objectName 文件名称
* @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/GetObject">AWS
* API Documentation</a>
*/
public S3Object getObjectInfo(String bucketName, String objectName) throws Exception {
@Cleanup
S3Object object = amazonS3.getObject(bucketName, objectName);
return object;
}
/**
* 删除文件
* @param bucketName bucket名称
* @param objectName 文件名称
* @throws Exception
* @see <a href=
* "http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/DeleteObject">AWS API
* Documentation</a>
*/
@Override
public void removeObject(String bucketName, String objectName) throws Exception {
amazonS3.deleteObject(bucketName, objectName);
}
@Override
public void afterPropertiesSet() {
ClientConfiguration clientConfiguration = new ClientConfiguration();
clientConfiguration.setMaxConnections(properties.getOss().getMaxConnections());
AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
properties.getOss().getEndpoint(), properties.getOss().getRegion());
AWSCredentials awsCredentials = new BasicAWSCredentials(properties.getOss().getAccessKey(),
properties.getOss().getSecretKey());
AWSCredentialsProvider awsCredentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);
this.amazonS3 = AmazonS3Client.builder().withEndpointConfiguration(endpointConfiguration)
.withClientConfiguration(clientConfiguration).withCredentials(awsCredentialsProvider)
.disableChunkedEncoding().withPathStyleAccessEnabled(properties.getOss().getPathStyleAccess()).build();
}
}
3.5 使用示例 (这里直接展示service层了,controller层在尝试试再自己补上吧)
@Service
@RequiredArgsConstructor
public class OssServiceImpl implements OssService {
private final FileProperties fileProperties;
private final OssTemplate ossTemplate;
@Override
public R upload(MultipartFile file) {
if (Objects.isNull(file)){
throw new BusinessException("上传文件不能为空");
}
String fileName = UUID.randomUUID()+ "." + FileUtil.extName(file.getOriginalFilename());
try(
InputStream inputStream = file.getInputStream()
) {
ossTemplate.putObject(fileProperties.getBucketName(),fileName,inputStream,file.getSize(),"application/octet-stream");
} catch (Exception e) {
e.printStackTrace();
throw new BusinessException("上传文件失败:"+e.getMessage());
}
return R.ok("上传成功",fileName);
}
}
四、结尾
感谢您的观看! 如果本文对您有帮助,麻烦用您发财的小手点个三连吧!
更多推荐
所有评论(0)