minio 高可用 (原理+秒懂+史上最全)

Posted 架构师-尼恩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了minio 高可用 (原理+秒懂+史上最全)相关的知识,希望对你有一定的参考价值。

文章很长,而且持续更新,建议收藏起来,慢慢读! Java 高并发 发烧友社群:疯狂创客圈(总入口) 奉上以下珍贵的学习资源:


推荐:入大厂 、做架构、大力提升Java 内功 的 精彩博文

入大厂 、做架构、大力提升Java 内功 必备的精彩博文2021 秋招涨薪1W + 必备的精彩博文
1:Redis 分布式锁 (图解-秒懂-史上最全)2:Zookeeper 分布式锁 (图解-秒懂-史上最全)
3: Redis与MySQL双写一致性如何保证? (面试必备)4: 面试必备:秒杀超卖 解决方案 (史上最全)
5:面试必备之:Reactor模式6: 10分钟看懂, Java NIO 底层原理
7:TCP/IP(图解+秒懂+史上最全)8:Feign原理 (图解)
9:DNS图解(秒懂 + 史上最全 + 高薪必备)10:CDN图解(秒懂 + 史上最全 + 高薪必备)
11: 分布式事务( 图解 + 史上最全 + 吐血推荐 )12:seata AT模式实战(图解+秒懂+史上最全)
13:seata 源码解读(图解+秒懂+史上最全)14:seata TCC模式实战(图解+秒懂+史上最全)

Java 面试题 30个专题 , 史上最全 , 面试必刷阿里、京东、美团… 随意挑、横着走!!!
1: JVM面试题(史上最强、持续更新、吐血推荐)2:Java基础面试题(史上最全、持续更新、吐血推荐
3:架构设计面试题 (史上最全、持续更新、吐血推荐)4:设计模式面试题 (史上最全、持续更新、吐血推荐)
17、分布式事务面试题 (史上最全、持续更新、吐血推荐)一致性协议 (史上最全)
29、多线程面试题(史上最全)30、HR面经,过五关斩六将后,小心阴沟翻船!
9.网络协议面试题(史上最全、持续更新、吐血推荐)更多专题, 请参见【 疯狂创客圈 高并发 总目录

SpringCloud 精彩博文
nacos 实战(史上最全) sentinel (史上最全+入门教程)
SpringCloud gateway (史上最全)更多专题, 请参见【 疯狂创客圈 高并发 总目录

背景:

下一个视频版本,从架构师视角,尼恩为大家打造高可用、高并发中间件的原理与实操。

目标:通过视频和博客的方式,为各位潜力架构师,彻底介绍清楚架构师必须掌握的高可用、高并发环境,包括但不限于:

  • 高可用、高并发nginx架构的原理与实操

  • 高可用、高并发mysql架构的原理与实操

  • 高可用、高并发nacos架构的原理与实操

  • 高可用、高并发rocketmq架构的原理与实操

  • 高可用、高并发es架构的原理与实操

  • 高可用、高并发minio架构的原理与实操

why 高可用、高并发中间件的原理与实操:

  • 实际的开发过程中,很多小伙伴聚焦crud开发,环境出了问题,都不能启动。

  • 作为架构师,或者未来想走向高端开发,或者做架构,必须掌握高可用、高并发中间件的原理,掌握其实操。

本系列博客的具体内容,请参见 Java 高并发 发烧友社群:疯狂创客圈

分布式文件系统应用场景

​ 互联网下海量的非结构化存储的需求背景下,比如:

  • 电商网站,存储海量的商品图片
  • 视频网站,海量的视频文件
  • 网盘,海量的文件
  • 社交网站等等

​ 在这样的背景下,传统的FastDFS部署太过于繁琐,动不动就是来个nginx,然后配置一堆参数和设置,尤其是做分布式的时候,那维护成本一下就上来了,从维护和部署的角度,FastDFS不是一个好的选择,而从迭代的角度,FastDFS早就不维护了,有很多需求是无法

支持到的,那么就需要你自己思考写源码打包了。

同理HDFS部署也不简单,而且Hadoop适合超大文件的存储,并且文件都需要分片,应用场景更多是计算处理,实时数据分析,并且其实HDFS比较吃硬件设备,因为偏于计算,所以对CPU的要求比较高,对于中小企业的业务量并没有这么大,所以应用场景这块也比较

难接触到,但是HDFS的功能还是十分强大的!!还是根据业务进行选型。

存储方案的选型

以下选型方案,来自于探探

这是探探app的几个功能,它的基础功能非常简单左划右划,左划是不喜欢,右划是喜欢,当两个人相互喜欢就配对成功了,配对成功之后就开始相互聊天。

你所看到的一些图片、视频还有在聊天中产生的语音,这些元素在对象存储的范畴里面,就是以对象保存下来。

随着用户量的增长,探探所要存储的对象越来越多。

探探存储的文件类型

这是探探2019年的文件存储数据.

  • 数据的体量为:

在不同类型对象所占的空间中,显而易见图片是存储的一个大头,超过了 1PB,它定期清理所以大小基本上保持了稳定的状态。

  • 访问的吞吐量数据为:

写入的 QPS 大概是 1 千左右,读取是 5 千左右,压力不是很大。

方案选型

于是,探探团队就开始调研当前的一些开源方案,这些存储方案里面可以分为两种:

  • 一种是可以自定对象名称的;

  • 另外一种是系统自动生成对象名称。

探探的对象名是自己生成的,里面包含了业务逻辑。

像 FS 就是国内大佬开源的一个分支存储,但是因为不能自定义文件名所以不合适,左划掉。

还有像领英的 Ambry、MogileFS 其实都不能自定对象名的,所以是不合适的。

左上角 LeoFS 对探探来说不是很可控,所以不合适。

TFS 是淘宝开源的,但是目前已经很少有人维护它并且也不是很活跃,所以当时就没有考虑。

ceph 是一个比较强大的分布式存储,但是它整个系统非常复杂需要大量的人力进行维护,和探探的产品不是很符合,所以暂时不考虑。

GlusterFS 为本身是一个非常成熟的对象存储的方案。2011年左右被收购了,他们原版人马又做了另外一个存储系统MINIO,仙鹤就是他们的 logo。

MINIO 的文档非常详细、具体,再加上他们之前在存储方面有十几年的经验,所以就这样打动了探探团队,作为选型的标的。

MinIO 介绍

Minio 是个基于 Golang 编写的开源对象存储套件,虽然轻量,却拥有着不错的性能。

官网地址:MinIO | High Performance, Kubernetes Native Object Storage

何为对象存储?

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

MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。

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

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

对于中小型企业,如果不选择存储上云,那么 Minio 是个不错的选择,麻雀虽小,五脏俱全。

当然 Minio 除了直接作为对象存储使用,还可以作为云上对象存储服务的网关层,无缝对接到 Amazon S3、MicroSoft Azure。

MINIO 基础概念

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尽可能分布在不同的节点上

Set /Drive 的关系

Set /Drive 这两个概念是 MINIO 里面最重要的两个概念,一个对象最终是存储在 Set 上面的。

我们来看下边 MINIO 集群存储示意图,每一行是一个节点机器,这有 32 个节点,每个节点里有一个小方块我们称之 Drive,Drive 可以简单地理解为一个硬盘。

图中,一个节点有 32 个 Drive,相当于 32 块硬盘。

Set 是另外一个概念,Set 是一组 Drive 的集合,图中,所有蓝色、橙色背景的Drive(硬盘)的就组成了一个 Set.

MIINO如何写入对象?

MINIO 是通过数据编码,将原来的数据编码成 N 份,N 就是一个 Set 上面 Drive 的数量,后面多次提到的 N 都是指这个意思。

上图中,一个 Set 上面 Drive 的数量,是3.

对象被编码成N份之后,把每一份,写到对应的 Drive 上面,这就是把一个对象存储在整个 Set 上。

一个集群包含多个 Set,每个对象最终存储在哪个 Set 上是根据对象的名称进行哈希,然后影射到唯一的 Set 上面,这个方式从理论上保证数据可以均匀的分布到所有的 Set 上。

根据的观测,数据分布的也非常均匀,一个 Set 上包含多少个 Drive 是由系统自动根据集群规模算出来的,当然,也可以自己去配置。

一个 Set 的 Drive 系统会考虑尽可能把它放在多的节点上面,保证它的可靠性。

Minio存储架构

Minio针对不同应用场景也设置了对应的存储架构:

单主机,单硬盘模式

该模式下,Minio只在一台服务器上搭建服务,且数据都存在单块磁盘上,该模式存在单点风险,主要用作开发、测试等使用

启动的命令为:

minio --config-dir ~/tenant1 server --address :9001 /disk1/data/tenant1

单主机,多硬盘模式

该模式下,Minio在一台服务器上搭建服务,但数据分散在多块(大于4块)磁盘上,提供了数据上的安全保障

minio --config-dir ~/tenant1 server --address :9001 /disk1/data/tenant1 /disk2/data/tenant1 /disk3/data/tenant1 /disk4/data/enant1

多主机、多硬盘模式(分布式)

该模式是Minio服务最常用的架构,通过共享一个access_key和secret_key,在多台(2-32)服务器上搭建服务,且数据分散在多块(大于4块,无上限)磁盘上,提供了较为强大的数据冗余机制(Reed-Solomon纠删码)。

export MINIO_ACCESS_KEY=<TENANT1_ACCESS_KEY>
export MINIO_SECRET_KEY=<TENANT1_SECRET_KEY>
minio --config-dir ~/tenant1 server --address :9001 http://192.168.10.11/data/tenant1 http://192.168.10.12/data/tenant1 http://192.168.10.13/data/tenant1 http://192.168.10.14/data/tenant1

分布式Minio有什么好处?

在大数据领域,通常的设计理念都是无中心和分布式。Minio分布式模式可以帮助你搭建一个高可用的对象存储服务,你可以使用这些存储设备,而不用考虑其真实物理位置。

数据保护

分布式Minio采用 纠删码来防范多个节点宕机和位衰减bit rot

分布式Minio至少需要4个硬盘,使用分布式Minio自动引入了纠删码功能。

高可用

单机Minio服务存在单点故障,相反,如果是一个有N块硬盘的分布式Minio, 只要有N/2硬盘在线,你的数据就是安全的。

不过你需要至少有N/2+1个硬盘来创建新的对象。

例如,一个16节点的Minio集群,每个节点16块硬盘,就算8台服務器宕机,这个集群仍然是可读的,不过你需要9台服務器才能写数据。

注意,只要遵守分布式Minio的限制,你可以组合不同的节点和每个节点几块硬盘。

比如,你可以使用2个节点,每个节点4块硬盘,也可以使用4个节点,每个节点两块硬盘,诸如此类。

一致性

Minio在分布式和单机模式下,所有读写操作都严格遵守read-after-write一致性模型。

MinIO的数据高可靠

Minio使用了Erasure Code 纠删码Bit Rot Protection 数据腐化保护这两个特性,所以MinIO的数据可靠性做的高。

Erasure Code纠删码

纠删码(Erasure Code)简称EC,是一种数据保护方法,它将数据分割成片段,把冗余数据块扩展、编码,并将其存储在不同的位置,比如磁盘、存储节点或者其它地理位置。

从数据函数角度来说,纠删码提供的保护可以用下面这个简单的公式来表示:n = k + m。变量“k”代表原始数据或符号的值。变量“m”代表故障后添加的提供保护的额外或冗余符号的值。变量“n”代表纠删码过程后创建的符号的总值。

举个例子,假设n=16,代表有16块磁盘,另外,有10份原始文件一模一样,称为k,16 = 10 +m,这个m就是可以恢复的校验块个数,所以m是6,任意6个不可用,原始文件都可以恢复,极端情况,10个原始文件坏掉6个,靠4个原始的加上6个校验块,可以把坏掉的6个原始文件恢复,这个用到数学行列式矩阵知识,不做展开。

MinIO的编码方式,将一个对象编码成若干个数据块和校验块,我们简称为Erasure Code码,这个是编码的类型,这种编码的类型,还需要算法来实现,minio 采用的是 Reed-Solomon算法。

MinIO使用Reed-Solomon算法,该算法把对象编码成若干个数据块和校验块。

Reed-Solomon算法的特点:

  • 低冗余
  • 高可靠

为了表述方便,把数据块和校验块统称为编码块,之后我们可以通过编码块的一部分就能还原出整个对象。

Reed-Solomon code

Reed-Solomon 是纠删码的实现算法的一种,当然,也是一种恢复丢失和损坏数据的数学算法,

Minio默认采用Reed-Solomon code将数据拆分成N/2个数据块和N/2个奇偶校验块。

这就意味着如果是16块盘,一个对象会被分成8个数据块、8个奇偶校验块,你可以丢失任意8块盘(不管其是存放的数据块还是校验块),你仍可以从剩下的盘中的数据进行恢复。

如上图,如我们所知,一个对象存储在一个Set上面,这个Set包含16个Drive,其中灰色的一半是数据库,橙色的一半是校验块,这种方式最多能忍受一半的编码丢失或损坏。

所有编码块的大小是原对象的2倍,跟传统多副本存储方案相比,他只冗余存了一份,但可靠性更高。

纠删码的工作原理和RAID或者副本不同,像RAID6只能在损失两块盘,或者以下的情况下不丢数据,而Minio纠删码可以在丢失一半的盘的情况下,仍可以保证数据安全。

而且Minio纠删码是作用在对象级别,可以一次恢复一个对象,而RAID是作用在卷级别,数据恢复时间很长。

Minio对每个对象单独编码,存储服务一经部署,通常情况下是不需要更换硬盘或者修复。

此外,针对不同应用所需的数据安全级别不同,Minio还提供了存储级别(Storage Class)的配置,调整数据块和校验块的比例,做到对空间的最佳使用。

比如在将比例调整为14:2后,存储100M的数据占用的空间仅为114M。

Bit Rot Protection:

接下来讲Bit Rot Protection,直译就是腐烂。

它只是物理设备上的一些文件细微的损坏,还没有被操作系统所硬件所察觉,但是他已经损坏了。

Bit Rot 位衰减又被称为数据腐化Data Rot无声数据损坏Silent Data Corruption,

位衰减可以理解为无形中的数据丢失——或者称为“Bit rot”, 是指物理存储介质的衰减所带来的隐患将凸显出来。

位衰减是目前硬盘数据的一种严重数据丢失问题。

硬盘上的数据可能会神不知鬼不觉就损坏了,也没有什么错误日志。

一项对150万块硬盘的研究表明,每90块硬盘就有1块有这种“软错误”,这个错误不但会导致数据丢失,还会导致RAID错误。

针对这一问题,最新的Minio采用了HighwayHash算法计算校验和来防范位衰减,根据测试结果,其可以实现10GB/s的处理速度。

大致的做法是:

MinIO把之前的编码块进行 HighwayHash 编码,最后要校验这个编码,以确保每个编码是正确的。

文件的修复

另外,MinIO提供了一个管理工具,可以对所有编码块进行校验,如果发现编码块有问题,再去修复它。

得益于Reed-Solomon纠删码,Minio可以更加灵活的对文件进行修复。

目前,Minio提供了全量、bucket、文件夹、文件等各个粒度的修复操作:

递归修复


$ mc admin heal -r myminio

指定桶修复

$ mc admin heal -r myminio/dev

下面是几个例子:

相比一般的RAID方式,Minio可以在非常小的粒度下对文件进行修复操作,灵活性有了很大提高。

修复后,可以JSON格式列出指定路径(文件、大小)


$ mc ls -r --json myminio/dev

{
 "status": "success",
 "type": "file",
 "lastModified": "2019-05-30T09:43:07.763-04:00",
 "size": 44996289,
 "key": "myFile",
 "etag": "bae58dc18zzzzz54c14e233b520e0a"
}

部署实操

Minio 提供了两种部署方式:

  • 单机部署
  • 分布式

两种部署方式都非常简单,其中分布式部署还提供了纠删码功能来降低数据丢失的风险。

单机部署:

minio server /data

分布式部署:

export MINIO_ACCESS_KEY=<ACCESS_KEY>
export MINIO_SECRET_KEY=<SECRET_KEY>
minio server http://host{1...n}/export{1...m} http://host{1...o}/export{1...m}

Docker Compose部署

官方的文档:

https://docs.min.io/cn/deploy-minio-on-docker-compose.html

Docker Compose允许定义和运行单主机,多容器Docker应用程序。

使用Compose,您可以使用Compose文件来配置MinIO服务。 然后,使用单个命令,您可以通过你的配置创建并启动所有分布式MinIO实例。 分布式MinIO实例将部署在同一主机上的多个容器中。 这是建立基于分布式MinIO的开发,测试和分期环境的好方法。

环境说明

操作系统ip地址docker版本Docker Compose版本
centos 7.6192.168.31.3419.03.81.24.1

运行分布式MinIO

在Docker Compose上部署分布式MinIO,请下载docker-compose.yaml到你的当前工作目录。Docker Compose会pull MinIO Docker Image,所以你不需要手动去下载MinIO binary。然后运行下面的命令

docker-compose pull
docker-compose up -d

现在每个实例都可以访问,端口从9001到9004,请在浏览器中访问http://127.0.0.1:9001/

注意事项

  • 默认情况下Docker Compose file使用的是最新版的MinIO server的Docker镜像,你可以修改image tag来拉取指定版本的MinIO Docker image.

  • 默认情况下会创建4个minio实例,你可以添加更多的MinIO服务(最多总共16个)到你的MinIO Comose deployment。添加一个服务

    • 复制服务定义并适当地更改新服务的名称。
    • 更新每个服务中的命令部分。
    • 更新要为新服务公开的端口号。 另外,请确保分配给新服务的端口尚未使用。

    关于分布式MinIO的更多资料,请访问这里.

  • Docker compose file中的MinIO服务使用的端口是9001到9004,这允许多个服务在主机上运行。

访问MinIO

http://192.168.31.34:9001/minio/

用户名:minio,密码:minio123

登录之后,效果如下:

MinIO操作

修改密码

点击右上角

提示:无法通过浏览器更新此用户的凭据

注意:如果需要修改密码,修改docker-compose.yaml中的MINIO_ACCESS_KEY和MINIO_SECRET_KEY变量即可。

创建bucket(文件夹)

先来创建一个bucket

创建data

上传文件

选中data,点击右下角的上传文件

选择一张图片,上传成功之后,效果如下:

查看bucket文件信息

点击data,查看与设置该Object的基本信息:
查看共享地址Shareable Link

设置到期时间,最大可保存时间为7天

对话框上方弹出该Object现剩余到期时间

附录1:Minio纠删码的补充学习

Minio使用纠删码erasure code和校验和checksum来保护数据免受硬件故障和无声数据损坏。 即便您丢失一半数量(N/2)的硬盘,您仍然可以恢复数据。

什么是纠删码erasure code?

纠删码是一种恢复丢失和损坏数据的数学算法, Minio采用Reed-Solomon code 算法将对象拆分成N/2数据和N/2 奇偶校验块。 这就意味着如果是12块盘,一个对象会被分成6个数据块、6个奇偶校验块,你可以丢失任意6块盘(不管其是存放的数据块还是奇偶校验块),你仍可以从剩下的盘中的数据进行恢复,是不是很NB,感兴趣的同学请翻墙google。

为什么纠删码有用?

纠删码的工作原理和RAID或者复制不同,像RAID6可以在损失两块盘的情况下不丢数据,而Minio纠删码可以在丢失一半的盘的情况下,仍可以保证数据安全。 而且Minio纠删码是作用在对象级别,可以一次恢复一个对象,而RAID是作用在卷级别,数据恢复时间很长。 Minio对每个对象单独编码,存储服务一经部署,通常情况下是不需要更换硬盘或者修复。Minio纠删码的设计目标是为了性能和尽可能的使用硬件加速。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EfJGx07v-1634650700405)(https://github.com/minio/minio/blob/master/docs/screenshots/erasure-code.jpg?raw=true)]

Minio纠删码实验

1. 前提条件:

安装Minio- Minio快速入门

2. 以纠删码模式运行Minio

示例: 使用Minio,在12个盘中启动Minio服务。

minio server /data1 /data2 /data3 /data4 /data5 /data6 /data7 /data8 /data9 /data10 /data11 /data12

示例: 使用Minio Docker镜像,在8块盘中启动Minio服务。

docker run -p 9000:9000 --name minio \\
  -v /mnt/data1:/data1 \\
  -v /mnt/data2:/data2 \\
  -v /mnt/data3:/data3 \\
  -v /mnt/data4:/data4 \\
  -v /mnt/data5:/data5 \\
  -v /mnt/data6:/data6 \\
  -v /mnt/data7:/data7 \\
  -v /mnt/data8:/data8 \\
  minio/minio server /data1 /data2 /data3 /data4 /data5 /data6 /data7 /data8

3. 验证是否设置成功

你可以随意拔掉硬盘,看Minio是否可以正常读写。

附录2:什么是位衰减bit rot保护?

位衰减又被称为数据腐化Data Rot、无声数据损坏Silent Data Corruption,是目前硬盘数据的一种严重数据丢失问题。

硬盘上的数据可能会神不知鬼不觉就损坏了,也没有什么错误日志。

正所谓明枪易躲,暗箭难防,这种背地里犯的错比硬盘直接咔咔宕了还危险。

不过不用怕,Minio纠删码采用了高速 HighwayHash 基于哈希的校验和来防范位衰减。

附录3:Springboot集成MinIO基础实现

新建一个项目springboot-minio,主要代码:

@RestController
@Slf4j
public class FileController {

    @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 ResultBean upload(@RequestParam(name = "file", required = false)
                              MultipartFile[] file) {
        ResultBean resultBean = ResultBean.newInstance();
        if (file == null || file.length == 0) {
            return resultBean.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 resultBean.error("上传失败");
            }
        }
        Map<String, Object> data = new HashMap<String, Object>();
        data.put("bucketName", bucketName);
        data.put("fileName", orgfileNameList);
        return resultBean.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 ResultBean delete(@PathVariable("fileName") String fileName) {
        ResultBean resultBean = ResultBean.newInstance();
        try {
            minioClient.removeObject(
                    RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
        } catch (Exception e) {
            log.error(e.getMessage());
            return resultBean.error("删除失败");
        }
        return resultBean.ok("删除成功", null);
    }

    private static String formatFileSize(long fileS) {
        DecimalFormat df = new DecimalFormat("#.00");
        String fileSizeString = "";
        String wrongSize = "0B";
        if (fileS == 高可用 Canal集群( 秒懂 + 史上最全)

rocket高可用 (图解+秒懂+史上最全)

nacos高可用(图解+秒懂+史上最全)

Docker原理(图解+秒懂+史上最全)

mysql pxc集群 原理 (图解+秒懂+史上最全)

Kafka源码分析9:Controller控制器的原理(图解+秒懂+史上最全)