写点什么

分布式文件存储系统 Minio 实战

用户头像
程序员Fox
关注
发布于: 58 分钟前
分布式文件存储系统Minio实战

1.分布式文件系统应用场景

互联网海量非结构化数据的存储需求


  • 电商网站:海量商品图片

  • 视频网站:海量视频文件

  • 网盘 : 海量文件

  • 社交网站:海量图片

1.1 Minio 介绍

MinIO 是一个基于 Apache License v2.0 开源协议的对象存储服务。它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几 kb 到最大 5T 不等。


MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。

官网:https://min.io/ http://www.minio.org.cn/


对象存储服务(Object Storage Service,OSS)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优化存储成本。


对于中小型企业,如果不选择存储上云,那么 Minio 是个不错的选择,麻雀虽小,五脏俱全。当然 Minio 除了直接作为对象存储使用,还可以作为云上对象存储服务的网关层,无缝对接到 Amazon S3、MicroSoft Azure。


在中国:阿里巴巴、腾讯、百度、中国联通、华为、中国移动等等 9000 多家企业也都在使用 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(老版本,最新版本如下图),前者是编码数据块及检验块,后者是元数据文件。


1.5 存储方案


2. Minio 环境搭建

Minio环境搭建


3. Minio Java Client 使用

MinIO Java Client SDK 提供简单的 API 来访问任何与 Amazon S3 兼容的对象存储服务。

官方 demo: https://github.com/minio/minio-java

官方文档:https://docs.min.io/docs/java-client-api-reference.html

引入依赖

<dependency>    <groupId>io.minio</groupId>    <artifactId>minio</artifactId>    <version>8.3.0</version></dependency>
<dependency> <groupId>me.tongfei</groupId> <artifactId>progressbar</artifactId> <version>0.5.3</version></dependency>
<dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.8.1</version></dependency>
复制代码


3.1 文件上传

public class FileUploader {  public static void main(String[] args)          throws IOException, NoSuchAlgorithmException, InvalidKeyException {    try {      // Create a minioClient with the MinIO server playground, its access key and secret key.      MinioClient minioClient =              MinioClient.builder()                      .endpoint("http://192.168.3.14:9000")                      .credentials("admin", "12345678")                      .build();
// 创建bucket String bucketName = "tulingmall"; boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); if (!exists) { // 不存在,创建bucket minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); }
// 上传文件 minioClient.uploadObject( UploadObjectArgs.builder() .bucket(bucketName) .object("tuling-mall-master.zip") .filename("F:\\mall\\tuling-mall-master.zip") .build()); System.out.println("上传文件成功"); } catch (MinioException e) { System.out.println("Error occurred: " + e); System.out.println("HTTP trace: " + e.httpTrace()); } }}
复制代码


3.2 文件下载

public class DownLoadDemo {
public static void main(String[] args) {
// Create a minioClient with the MinIO server playground, its access key and secret key. MinioClient minioClient = MinioClient.builder() .endpoint("http://192.168.3.14:9000") .credentials("admin", "12345678") .build();
// Download object given the bucket, object name and output file name try { minioClient.downloadObject( DownloadObjectArgs.builder() .bucket("tulingmall") .object("fox/fox.jpg") .filename("fox.jpg") .build());
} catch (Exception e) { e.printStackTrace(); }
}}
复制代码

3.3 Spring boot 整合 minio

构建 MinioClient 对象,并交给 spring 管理

@Data@Component@ConfigurationProperties(prefix = "minio")public class MinioProperties {
private String endpoint; private String accessKey; private String secretKey;
}
//ymlminio: endpoint: http://192.168.3.14:9000 accesskey: admin secretKey: 12345678
@Configurationpublic class MinioConfig {
@Autowired private MinioProperties minioProperties;
@Bean public MinioClient minioClient(){ MinioClient minioClient = MinioClient.builder() .endpoint(minioProperties.getEndpoint()) .credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey()) .build(); return minioClient; }}
复制代码

实现文件上传,下载,删除操作

@RestController@Slf4jpublic class MinioController {
@Autowired private MinioClient minioClient;
@Value("${minio.bucketName}") private String bucketName;

@GetMapping("/list") public List<Object> list() throws Exception { //获取bucket列表 Iterable<Result<Item>> myObjects = minioClient.listObjects( ListObjectsArgs.builder().bucket(bucketName).build()); Iterator<Result<Item>> iterator = myObjects.iterator(); List<Object> items = new ArrayList<>(); String format = "{'fileName':'%s','fileSize':'%s'}"; while (iterator.hasNext()) { Item item = iterator.next().get(); items.add(JSON.parse(String.format(format, item.objectName(), formatFileSize(item.size())))); } return items; }
@PostMapping("/upload") public Res upload(@RequestParam(name = "file", required = false) MultipartFile[] file) {

if (file == null || file.length == 0) { return Res.error("上传文件不能为空"); }
List<String> orgfileNameList = new ArrayList<>(file.length);
for (MultipartFile multipartFile : file) { String orgfileName = multipartFile.getOriginalFilename(); orgfileNameList.add(orgfileName); try { //文件上传 InputStream in = multipartFile.getInputStream(); minioClient.putObject( PutObjectArgs.builder().bucket(bucketName).object(orgfileName).stream( in, multipartFile.getSize(), -1) .contentType(multipartFile.getContentType()) .build()); in.close();
} catch (Exception e) { log.error(e.getMessage()); return Res.error("上传失败"); } }
Map<String, Object> data = new HashMap<String, Object>(); data.put("bucketName", bucketName); data.put("fileName", orgfileNameList); return Res.ok("上传成功",data); }
@RequestMapping("/download/{fileName}") public void download(HttpServletResponse response, @PathVariable("fileName") String fileName) {
InputStream in = null; try { // 获取对象信息 StatObjectResponse stat = minioClient.statObject( StatObjectArgs.builder().bucket(bucketName).object(fileName).build()); response.setContentType(stat.contentType()); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")); //文件下载 in = minioClient.getObject( GetObjectArgs.builder() .bucket(bucketName) .object(fileName) .build()); IOUtils.copy(in, response.getOutputStream()); } catch (Exception e) { log.error(e.getMessage()); } finally { if (in != null) { try { in.close(); } catch (IOException e) { log.error(e.getMessage()); } } }
}
@DeleteMapping("/delete/{fileName}") public Res delete(@PathVariable("fileName") String fileName) { try { minioClient.removeObject( RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build()); } catch (Exception e) { log.error(e.getMessage()); return Res.error("删除失败"); } return Res.ok("删除成功",null); }
private static String formatFileSize(long fileS) { DecimalFormat df = new DecimalFormat("#.00"); String fileSizeString = ""; String wrongSize = "0B"; if (fileS == 0) { return wrongSize; } if (fileS < 1024) { fileSizeString = df.format((double) fileS) + " B"; } else if (fileS < 1048576) { fileSizeString = df.format((double) fileS / 1024) + " KB"; } else if (fileS < 1073741824) { fileSizeString = df.format((double) fileS / 1048576) + " MB"; } else { fileSizeString = df.format((double) fileS / 1073741824) + " GB"; } return fileSizeString; }
}
复制代码


发布于: 58 分钟前阅读数: 3
用户头像

程序员Fox

关注

思想比技术更重要,有术无道止于术 2019.03.12 加入

多年中间件开发经验,擅长分布式,微服务架构技术,精通各大源码框架,源码控,喜欢分享技术

评论

发布
暂无评论
分布式文件存储系统Minio实战