常见对象存储技术选型。
存储的方案分成两种:一种是可以自定对象名称的,另一种是系统自动生成对象名称。
- 不能自定义名称的有领英的
Ambry
,MogileFS
。 TFS
是淘宝开源的,但是目前已经很少有人维护它并且也不是很活跃。ceph
是一个比较强大的分布式存储,但是它整个系统非常复杂需要大量的人力进行维护。GlusterFS
为本身是一个非常成熟的对象存储的方案,2011被收购了,原班的人马又做了另外一个存储系统MINIO
。
各家云厂商的对象存储服务
- AWS: Simple Storage Service(简称 S3)
- Azure: Azure Blob Stroage
- Google Cloud: Google Cloud Storage
- 阿里云: 对象存储 OSS(Object Storage Service)
- 腾讯云:对象存储 COS(Cloud Object Storage)
minioAPI文档地址:https://min.io/docs/minio/kubernetes/upstream/index.html?ref=docs-redirect
对象存储
-
它有什么特点?
- 容量无限大:可以到 EB 级,多少数据都能存的下
- 持久可靠:11个 9 甚至以上的可靠性,数据丢失的概率比中五百万的概率还要低 2-3 个量级
- 低成本:1 部高清电影存 1 年,差不多也就几块钱人民币
- 使用方便:支持 REST 接口,主要操作为 PUT/GET/DELETE等,使用非常简单。
-
对象存储的应用场景:
应用于各种类型的海量数据的存储。早期常见于非结构化数据的存储,比如日志、文本、音频、视频等,也正是为了实现这些海量非结构化数据的低成本存储,才催生了对象存储这个技术;近年来随着数据湖和湖仓一体架构的流行,对象存储也被越来越多的被用于结构化数据的存储。
MINIO
- minio是一个基于Apache License V2.0开源协议的对象存储服务,它兼容亚马逊S3云存储服务,非常适合于存储大容量非结构化的数据,如图片,视频,日志文件等。而一个对象文件可以任意大小,从几KB到最大的5T不等。它是一个非常轻量级的服务,可以很简单的和其它的应用结合,类似于NodeJS, Redis或者MySQL。
- minio默认不计算MD5,除非传输给客户端的时候,所以很快,支持windows,有web页进行管理。
- 推荐一个比较好的实践案例:基于 Go 开源项目 MIMIO 的对象存储方案在探探的实践:https://mp.weixin.qq.com/s/YIKB
springboot集成minio
依赖
<!-- minio start -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio-version}</version>
</dependency>
<!-- minio end -->
配置文件
minio.url=172.21.72.226
minio.port=9000
minio.accessKey=minioadmin
minio.secretKey=minioadmin
# 桶名称,一般一个项目一个桶
minio.bucketName=xxx
minio.secure=false
实现配置类,加载以上配置后实例一个minio客户端注册到spring容器供应用使用
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {
/**
* 服务器地址:域名,IPv4或者IPv6地址
*/
private String url;
/**
* 端口
*/
private Integer port;
/**
* accessKey类似于用户ID,用于唯一标识你的账户
*/
private String accessKey;
/**
* secretKey是你账户的密码
*/
private String secretKey;
/**
* 如果是true,则用的是https而不是http,默认值是true
*/
private Boolean secure;
/**
* 默认存储桶
*/
private String bucketName;
@Bean
public MinioClient getMinClient() {
return MinioClient.builder()
.endpoint(url, port, secure)
.credentials(accessKey, secretKey)
.build();
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public Boolean getSecure() {
return secure;
}
public void setSecure(Boolean secure) {
this.secure = secure;
}
public String getBucketName() {
return bucketName;
}
public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}
}
有了MinioClient之后,就可以根据其api进行对象存储操作了。通常不建议直接操作其原生api,可以封装一个工具辅助类
以下为示例代码:
@Component
public class MinioClientHelper {
private static final ZLogger logger = ZLoggerFactory.getLogger(MinioClientHelper.class, KeyConsts.LOG_MODULE);
@Autowired
private MinioClient minioClient;
/**
* 默认存储桶名称
*/
@Value("${minio.bucketName}")
private String defaultBucketName;
/**
* 上传
*
* @param filePath 上传文件路径
*/
public void upload(String filePath) {
uploadFile(filePath, "", "", defaultBucketName);
}
/**
* 上传
*
* @param filePath 上传文件路径
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
*/
public void upload(String filePath, String minioFileName) {
uploadFile(filePath, "", minioFileName, defaultBucketName);
}
/**
* 上传
*
* @param filePath 上传文件路径
* @param minioFilePath 上传文件在minio中的路径(如:/001/002/)
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
*/
public void upload(String filePath, String minioFilePath, String minioFileName) {
uploadFile(filePath, minioFilePath, minioFileName, defaultBucketName);
}
/**
* 上传
*
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
* @param in 文件流
*/
public void upload(String minioFileName, InputStream in) {
uploadFile("", minioFileName, in, defaultBucketName);
}
/**
* 上传
*
* @param minioFilePath 上传文件在minio中的路径(如:/001/002/)
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
* @param in 文件流
*/
public void upload(String minioFilePath, String minioFileName, InputStream in) {
uploadFile(minioFilePath, minioFileName, in, defaultBucketName);
}
/**
* 下载
*
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
* @return
*/
public byte[] download(String minioFileName) {
return download("", minioFileName, defaultBucketName);
}
/**
* 下载
*
* @param minioFilePath 上传文件在minio中的路径(如:/001/002/)
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
* @return
*/
public byte[] download(String minioFilePath, String minioFileName) {
return download(minioFilePath, minioFileName, defaultBucketName);
}
/**
* 获取预览文件绝对路径
*
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
* @return
*/
public String getPreviewFileUrl(String minioFileName) {
return getPreviewFileUrl("", minioFileName, defaultBucketName);
}
/**
* 获取预览文件绝对路径
*
* @param minioFilePath 上传文件在minio中的路径(如:001/002/)
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
* @return
*/
public String getPreviewFileUrl(String minioFilePath, String minioFileName) {
return getPreviewFileUrl(minioFilePath, minioFileName, defaultBucketName);
}
/**
* 删除
*
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
* @return
*/
public void remove(String minioFileName) {
remove("", minioFileName, defaultBucketName);
}
/**
* 删除
*
* @param minioFilePath 上传文件在minio中的路径(如:/001/002/)
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
* @return
*/
public void remove(String minioFilePath, String minioFileName) {
remove(minioFilePath, minioFileName, defaultBucketName);
}
/**
* 删除文件夹
* @param minioFilePath
*/
public void removeFolder(String minioFilePath) {
if (minioFilePath.startsWith(SymbolConsts.SLASH)) {
minioFilePath = minioFilePath.substring(1);
}
if (!minioFilePath.endsWith(SymbolConsts.SLASH)) {
minioFilePath = minioFilePath + SymbolConsts.SLASH;
}
try {
bucketFound(defaultBucketName);
Iterable<Result<Item>> objects = minioClient.listObjects(ListObjectsArgs.builder()
.bucket(defaultBucketName)
.startAfter(minioFilePath)
.recursive(true)
.build());
for (Result<Item> result : objects) {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(defaultBucketName)
.object(result.get().objectName()).build());
}
}
catch (Exception e) {
logger.error(ErrorConsts.SYSTEM_ERROR.getErrorCode(), e, e.getMessage());
}
}
/**
* 获取文件信息
*
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
*/
public StatObjectResponse getFileInfo(String minioFileName) {
return getFileInfo("", minioFileName, defaultBucketName);
}
/**
* 获取文件信息
*
* @param minioFilePath 上传文件在minio中的路径(如:/001/002/)
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
*/
public StatObjectResponse getFileInfo(String minioFilePath, String minioFileName) throws Exception {
return getFileInfo(minioFilePath, minioFileName, defaultBucketName);
}
/**
* 上传文件
*
* @param filePath 上传文件名称
* @param minioFilePath 上传文件在minio中的路径(如:/001/002/)
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
* @param bucket mini所在空间名称
*/
private void uploadFile(String filePath, String minioFilePath, String minioFileName, String bucket) {
if (UddpStringUtils.isAnyBlank(filePath, bucket)) {
return;
}
try {
// 存储桶构建
bucketBuild(bucket);
// 原始文件名称
String fileName = filePath.substring(
(filePath.lastIndexOf("/") > 0 ? filePath.lastIndexOf("/") : filePath.lastIndexOf("\\")) + 1);
// 保存的文件名称
minioFileName = minioFilePath + SymbolConsts.SLASH
+ (UddpStringUtils.isBlank(minioFileName) ? fileName : minioFileName);
minioClient.uploadObject(
UploadObjectArgs.builder().bucket(bucket).object(minioFileName).filename(filePath).build());
}
catch (Exception e) {
logger.error(ErrorConsts.INF_FILE_UPLOAD.getErrorCode(), e, e.getMessage());
}
}
/**
* 上传文件
*
* @param minioFilePath 上传文件在minio中的路径(如:/001/002/)
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
* @param in 文件流
* @param bucket
*/
private void uploadFile(String minioFilePath, String minioFileName, InputStream in, String bucket) {
try {
// 存储桶构建
bucketBuild(bucket);
// 原始文件名称
// 保存的文件名称
minioFileName = minioFilePath + SymbolConsts.SLASH
+ (UddpStringUtils.isBlank(minioFileName) ? System.currentTimeMillis() : minioFileName);
minioClient.putObject(
PutObjectArgs.builder().bucket(bucket).object(minioFileName).stream(in, in.available(), -1).build());
}
catch (Exception e) {
logger.error(ErrorConsts.INF_FILE_UPLOAD.getErrorCode(), e, e.getMessage());
}
}
/**
* 复制对象
*
* @param sourcePath
* @param destPath
* @param destName
*/
public void copyFile(String sourcePath, String destPath, String destName) {
try {
minioClient.copyObject(
CopyObjectArgs.builder().bucket(defaultBucketName).object(destPath + SymbolConsts.SLASH + destName)
.source(CopySource.builder().bucket(defaultBucketName).object(sourcePath).build()).build());
}
catch (Exception e) {
logger.error(ErrorConsts.INF_FILE_UPLOAD.getErrorCode(), e, e.getMessage());
}
}
/**
* 存储桶构建
*
* @param bucketName
*/
private void bucketBuild(String bucketName) {
try {
boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (!found) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
logger.info("minio存储桶Bucket【" + bucketName + "】创建成功!");
}
}
catch (Exception e) {
logger.error(ErrorConsts.INF_FILE_UPLOAD.getErrorCode(), e, e.getMessage());
}
}
/**
* 查找存储桶
*
* @param bucketName
*/
private void bucketFound(String bucketName) {
try {
boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (!found) {
ErrorConsts.INF_FILE_UPLOAD.throwOut("minio存储桶Bucket【" + bucketName + "】不存在");
}
}
catch (Exception e) {
logger.error(ErrorConsts.INF_FILE_UPLOAD.getErrorCode(), e, e.getMessage());
}
}
/**
* 下载
*
* @param minioFilePath 上传文件在minio中的路径(如:/001/002/)
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
* @param bucket minio所在空间名称
* @return
*/
private byte[] download(String minioFilePath, String minioFileName, String bucket) {
if (minioFilePath.startsWith(SymbolConsts.SLASH)) {
minioFilePath = minioFilePath.substring(1);
}
if (!minioFilePath.endsWith(SymbolConsts.SLASH)) {
minioFilePath = minioFilePath + SymbolConsts.SLASH;
}
InputStream in = null;
try {
bucketFound(bucket);
in = minioClient
.getObject(GetObjectArgs.builder().bucket(bucket).object(minioFilePath + minioFileName).build());
if (in != null) {
return ByteUtil.inputStreamToByteArray(in);
}
}
catch (Exception e) {
logger.error(ErrorConsts.SYSTEM_ERROR.getErrorCode(), e, "获取minio下载流异常");
}
finally {
if (in != null) {
try {
in.close();
}
catch (IOException e) {
logger.error(ErrorConsts.SYSTEM_ERROR.getErrorCode(), e, "关闭minio下载流异常");
}
}
}
return new byte[0];
}
/**
* 获取预览文件绝对路径(expiry:多少秒后链接失效,1800s后失效)
*
* @param minioFilePath 上传文件在minio中的路径(如:/001/002/)
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
* @param bucket minio所在空间名称
* @return
*/
private String getPreviewFileUrl(String minioFilePath, String minioFileName, String bucket) {
if (minioFilePath.startsWith(SymbolConsts.SLASH)) {
minioFilePath = minioFilePath.substring(1);
}
if (!minioFilePath.endsWith(SymbolConsts.SLASH)) {
minioFilePath = minioFilePath + SymbolConsts.SLASH;
}
String filePath = "";
try {
bucketFound(bucket);
filePath = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucket)
.object(minioFilePath + minioFileName).method(Method.GET).expiry(1800).build());
}
catch (Exception e) {
logger.error(ErrorConsts.SYSTEM_ERROR.getErrorCode(), e, "获取minio文件绝对路径异常");
}
return filePath;
}
/**
* 删除
*
* @param minioFilePath 上传文件在minio中的路径(如:/001/002/)
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
* @param bucket minio所在空间名称
*/
private void remove(String minioFilePath, String minioFileName, String bucket) {
if (minioFilePath.startsWith(SymbolConsts.SLASH)) {
minioFilePath = minioFilePath.substring(1);
}
if (!minioFilePath.endsWith(SymbolConsts.SLASH)) {
minioFilePath = minioFilePath + SymbolConsts.SLASH;
}
try {
bucketFound(bucket);
minioClient
.removeObject(RemoveObjectArgs.builder().bucket(bucket).object(minioFilePath + minioFileName).build());
}
catch (Exception e) {
logger.error(ErrorConsts.SYSTEM_ERROR.getErrorCode(), e, "获取minio文件绝对路径异常");
}
}
/**
* 获取文件信息
*
* @param minioFilePath 上传文件在minio中的路径(如:/001/002/)
* @param minioFileName 上传文件在minio中的名称(带文件后缀名)
* @param bucket minio所在空间名称
* @return
*/
private StatObjectResponse getFileInfo(String minioFilePath, String minioFileName, String bucket) {
if (minioFilePath.startsWith(SymbolConsts.SLASH)) {
minioFilePath = minioFilePath.substring(1);
}
if (!minioFilePath.endsWith(SymbolConsts.SLASH)) {
minioFilePath = minioFilePath + SymbolConsts.SLASH;
}
StatObjectResponse statObjectResponse = null;
try {
statObjectResponse = minioClient
.statObject(StatObjectArgs.builder().bucket(bucket).object(minioFilePath + minioFileName).build());
}
catch (Exception e) {
logger.error(ErrorConsts.SYSTEM_ERROR.getErrorCode(), e, "获取minio文件信息异常");
}
return statObjectResponse;
}
}
顺便提一下扩展写法,通过配置支持多种oss存储方案
配置
# 文件系统类型:fdfs、ctdfs、minio、oss
dfs.type=minio
# fdfs
fdfs.tracker-list[0]=10.45.46.235:22122
fdfs.tracker-list[1]=10.45.46.236:22122
fdfs.so-timeout=1000
fdfs.connect-timeout=3000
fdfs.thumb-image.width=150
fdfs.thumb-image.height=150
# 从池中借出的对象的最大数目(配置为-1表示不限制)
fdfs.pool.max-total=-1
# 获取连接时的最大等待时间
fdfs.pool.max-wait-millis=5000
# 每个key对应的池最大连接数
fdfs.pool.max-total-per-key=50
# 每个key对应的连接池最大空闲连接数
fdfs.pool.max-idle-per-key=10
# 每个key对应的连接池最小空闲连接数
fdfs.pool.min-idle-per-key=5
# ctdfs
ctdfs.url=
ctdfs.username=
ctdfs.passowrd=
ctdfs.scheme=
ctdfs.point=
ctdfs.timeout=3000
ctdfs.thumb-image.width=150
ctdfs.thumb-image.height=150
# 是否加密passowrd
ctdfs.decrypt=true
# minio
minio.endpoint=http://172.21.72.226:9000
minio.access-key=minioadmin
minio.secret-key=minioadmin
minio.bucket=autotest
minio.timeout=3000
minio.thumb-image.width=150
minio.thumb-image.height=150
# 是否加密secret-key
minio.decrypt=false
# oss
oss.endpoint=
oss.access-key-id=
oss.access-key-secret=
oss.bucket=autotest
oss.timeout=3000
oss.thumb-image.width=150
oss.thumb-image.height=150
# 是否加密access-key-secret
oss.decrypt=true
定义客户端接口
package com.iwhalecloud.autotest.common.dfs;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.commons.io.FileExistsException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import com.iwhalecloud.autotest.common.util.ExceptionUtil;
/**
* 文件系统客户端
* @author Admin
*/
public interface IDfsClient {
Logger logger = LoggerFactory.getLogger(IDfsClient.class);
/**
* 上传文件
* @param inputStream
* @param fileSize
* @param fileName
* @param fileInfoId
* @param datePath
* @return
*/
default String upload(InputStream inputStream, long fileSize, String fileName, String fileInfoId, boolean datePath) {
Assert.notNull(inputStream, "inputStream must not be null");
try {
byte[] bytes = IOUtils.toByteArray(inputStream);
return upload(bytes, fileName, fileInfoId, datePath);
}
catch (IOException e) {
throw ExceptionUtil.wrapToRuntimeException(e);
}
finally {
IOUtils.closeQuietly(inputStream, e -> {
logger.error(e.getMessage(), e);
});
}
}
/**
* 上传文件
* @param bytes
* @param fileName
* @param fileInfoId
* @param datePath
* @return
*/
String upload(byte[] bytes, String fileName, String fileInfoId, boolean datePath);
/**
* 下载文件
* @param bucket
* @param path
* @return
*/
default byte[] download(String bucket, String path) {
Assert.hasText(path, "path must not be empty");
try (InputStream input = downloadInputStream(bucket, path)) {
return IOUtils.toByteArray(input);
}
catch (IOException e) {
throw ExceptionUtil.wrapToRuntimeException(e);
}
}
/**
* 下载文件流
* @param bucket
* @param path
* @return
*/
default InputStream downloadInputStream(String bucket, String path) {
Assert.hasText(path, "path must not be empty");
byte[] bytes = download(bucket, path);
return new ByteArrayInputStream(bytes);
}
/**
* 下载并创建本地文件
* @param bucket
* @param path
* @param pathname
* @return
* @throws IOException
*/
default File download(String bucket, String path, String pathname) throws IOException {
Assert.hasText(path, "path must not be empty");
Assert.hasText(pathname, "pathname must not be empty");
Path filePath = Paths.get(pathname);
if (Files.exists(filePath)) {
throw new FileExistsException(filePath.toFile());
}
Files.createFile(filePath);
try (InputStream input = downloadInputStream(bucket, path)) {
File file = filePath.toFile();
FileUtils.copyInputStreamToFile(input, file);
return file;
}
}
/**
* 删除文件
* @param bucket
* @param path
* @param name
* @return
*/
boolean delete(String bucket, String path, String name);
/**
* 获取Bucket名称
* @return
*/
default String getBucket() {
return null;
}
}
提供各自实现
标签:存储,minio,对象,minioFileName,bucket,minioFilePath,param,String,MINIO From: https://www.cnblogs.com/acelin/p/17039419.html