https://blog.csdn.net/yvhqbat/article/details/115717052
网关启动
MinIO内部已经实现了GCS、S3、NAS等几个网关,支持的网关列表如下:
nas Network-attached storage (NAS)
azure Microsoft Azure Blob Storage
s3 Amazon Simple Storage Service (S3)
hdfs Hadoop Distributed File System (HDFS)
gcs Google Cloud Storage
假如要启动一个NAS网关,可以使用如下命令:
minio gateway nas PATH
以上命令中的PATH是一个NAS挂载点路径,当然你也可以使用本地路径。假如我的NAS挂载点路径为/tmp/nas/ ,那么我通过如下命令就可以启动一个NAS网关。
minio gateway nas /tmp/nas/
小提示:因为MinIO需要用户名和密码,所以在启动网关之前一定要设置,通过如下命令即可: export MINIO_ACCESS_KEY=accesskey export MINIO_SECRET_KEY=secretkey
其实就是设置MINIO_ACCESS_KEY和MINIO_SECRET_KEY环境变量,可以改成自己想要的用户名和密码。
启动后你可以像使用MinIO Server一样使用网关。
网关启动代码分析
MinIO的命令行启动只有2个命令,一个是server、一个是gateway,分别用于启动服务和网关,而整个MinIO的启动是从minio/main.go文件(假设存放MinIO源代码的根目录是minio)开始的。
https://cloud.tencent.com/developer/article/1731586
https://docs.min.io/docs/minio-server-configuration-guide.html
https://blog.csdn.net/flysnow_org/article/details/109172788
对象存储服务(Object Storage Service,OSS)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优化存储成本。
对于中小型企业,如果不选择存储上云,那么 Minio 是个不错的选择,麻雀虽小,五脏俱全。当然 Minio 除了直接作为对象存储使用,还可以作为云上对象存储服务的网关层,无缝对接到 Amazon S3、MicroSoft Azure。
Minio 提供了两种部署方式:单机部署和分布式,两种部署方式都非常简单,其中分布式部署还提供了纠删码功能来降低数据丢失的风险。
单机部署:
minio server /data
分布式部署:
export MINIO_ACCESS_KEY=
export MINIO_SECRET_KEY=
minio server http://host{1...n}/export{1...m} http://host{1...o}/export{1...m}
当然如果我们只有一台机器,但是想用纠删码的功能,也可以直接配置使用多个本地盘
minio server /data1 /data2 /data3 … /data8
https://zhuanlan.zhihu.com/p/148053565?from_voters_page=true
https://min.io/
特点
高性能:作为高性能对象存储,在标准硬件条件下它能达到55GB/s的读、35GG/s的写速率
可扩容:不同MinIO集群可以组成联邦,并形成一个全局的命名空间,并跨越多个数据中心
云原生:容器化、基于K8S的编排、多租户支持
Amazon S3兼容:Minio使用Amazon S3 v2 / v4 API。可以使用Minio SDK,Minio Client,AWS SDK和AWS CLI访问Minio服务器。
可对接后端存储: 除了Minio自己的文件系统,还支持DAS、 JBODs、NAS、Google云存储和Azure Blob存储。
SDK支持: 基于Minio轻量的特点,它得到类似Java、Python或Go等语言的sdk支持
Lambda计算: Minio服务器通过其兼容AWS SNS / SQS的事件通知服务触发Lambda功能。支持的目标是消息队列,如Kafka,NATS,AMQP,MQTT,Webhooks以及Elasticsearch,Redis,Postgres和MySQL等数据库。
有操作页面
功能简单: 这一设计原则让MinIO不容易出错、更快启动
支持纠删码:MinIO使用纠删码、Checksum来防止硬件错误和静默数据污染。在最高冗余度配置下,即使丢失1/2的磁盘也能恢复数据
存储机制
Minio使用纠删码erasure code和校验和checksum。 即便丢失一半数量(N/2)的硬盘,仍然可以恢复数据。
校验和
保护数据免受硬件故障和无声数据损坏
纠删码
纠删码是一种恢复丢失和损坏数据的数学算法,目前,纠删码技术在分布式存储系统中的应用主要有三类,阵列纠删码(Array Code: RAID5、RAID6等)、RS(Reed-Solomon)里德-所罗门类纠删码和LDPC(LowDensity Parity Check Code)低密度奇偶校验纠删码。Erasure Code是一种编码技术,它可以将n份原始数据,增加m份数据,并能通过n+m份中的任意n份数据,还原为原始数据。即如果有任意小于等于m份的数据失效,仍然能通过剩下的数据还原出来。
Minio采用Reed-Solomon code将对象拆分成N/2数据和N/2 奇偶校验块。 这就意味着如果是12块盘,一个对象会被分成6个数据块、6个奇偶校验块,可以丢失任意6块盘(不管其是存放的数据块还是奇偶校验块),仍可以从剩下的盘中的数据进行恢复。
RS code编码数据恢复原理
RS编码以word为编码和解码单位,大的数据块拆分到字长为w(取值一般为8或者16位)的word,然后对word进行编解码。 数据块的编码原理与word编码原理相同,后文中以word为例说明,变量Di, Ci将代表一个word。
把输入数据视为向量D=(D1,D2,…, Dn), 编码后数据视为向量(D1, D2,…, Dn, C1, C2,.., Cm),RS编码可视为如下(图1)所示矩阵运算。
图1最左边是编码矩阵(或称为生成矩阵、分布矩阵,Distribution Matrix),编码矩阵需要满足任意n*n子矩阵可逆。为方便数据存储,编码矩阵上部是单位阵(n行n列),下部是m行n列矩阵。下部矩阵可以选择范德蒙德矩阵或柯西矩阵。
https://www.jianshu.com/p/c2b43ff67df0
01、数据组织结构
NAS系统把整个存储资源组织为目录树的形式。与此不同,对象存储系统把存储资源组织为租户-桶-对象的形式。
对象:类似于hash表中的表项:它的名字相当于关键字,它的内容相当于“值”。
桶:是若干个对象的逻辑抽象,是盛装对象的容器。
租户:用于隔离存储资源。在租户之下可以建立桶、存储对象。
用户:在租户下面创建的用于访问不同桶的账号。可以使用MinIO提供的mc命令设置不用用户访问各个桶的权限。
02、数据分布与均衡
1)、去中心化架构
MinIO采用去中心化的无共享架构,对象数据被打散存放在不同节点的多块硬盘,对外提供统一命名空间访问,并通过Web负载均衡器或DNS轮询(DNS round-robin)在各服务器之间实现负载均衡。
2)、统一命名空间
MinIO对象存储系统主要有两种部署方式,一种是常见的本地分布式集群部署,一种是联盟模式部署。本地分布式集群部署方式即在多个本地服务器节点部署MinIO软件,并将其组件成单套分布式存储集群,并提供统一命名空间和标准S3访问接口。联盟部署模式即将多个MinIO集群在逻辑上组成了统一命名空间,实现近乎无限的扩展与海量的数据规模管理,这些集群可以都在本地,或分布在不同地域的数据中心
3)、分布式锁管理
与分布式数据库相类似,MinIO对象存储系统也面临数据一致性问题:一个客户端程序在读取一个对象的同时,另一个客户端程序可能正在修改或者删除这个对象。为了避免出现数据不一致情况,MinIO相关开发人员为MinIO对象存储专门设计并实现了dsync分布式锁管理器。它采用如下分布式锁管理机制:
任何一个节点的锁请求都会广播给集群内所有在线节点;
如果n/2 + 1个节点回应“是”,则成功获得锁;
客户端获得锁以后可保留任意时间,不需要时自己释放即可。释放操作也会广播给所有的节点,从而恢复锁的可用状态。写锁仅能被一个写入者获得。
设计目标
要求设计简单,因为简单的设计,可以避免程序中很多非常棘手的条件分支的支持。
不存在主节点,因为一旦在设计上引入主节点,那么如果主节点宕机,整个锁管理器机制即将失效,这对MinIO对象存储系统影响非常严重,是不可接受的。
系统必须是弹性的,即使存在多个失效的节点,只要它们的个数小于n/2, 整个锁管理系统是可以正常工作的。
完全可以替代Golang标准库中的sync.RWMutex互斥锁。这样可以简化MinIO对象存储系统的编程。
当失效节点重启以后,其它节点重新连接。
Zookeeper/raft功能丰富,而MinIO对象储存的使用用例其实很有限。在MinIO中使用zookeeper/raft,会使整个系统增加不必要的复杂性。
优势
实际操作极其简单,有效代码不足一千行,易理解,易维护。
超高的性能。
4)、云网关模式
MinIO存储系统的后端可以是磁盘,也可以作为云网关,对接第三方的NAS系统、分布式文件系统或公有云存储资源,并为业务系统转换提供标准的对象访问接口。
目前MinIO支持Google 云存储、HDFS、阿里巴巴OSS、亚马逊S3, 微软Azure Blob 存储等第三方存储资源。
03、元数据
1)、架构
MinIO对象存储系统无元数据数据库,所有的操作都是对象级别的粒度的。这种做法的优势是:
个别对象的失效,不会溢出为更大级别的系统失效。
便于实现“强一致性”这个特性。此特性对于机器学习与大数据处理非常重要。
2)、管理
元数据与数据一起存放在磁盘上:数据部分纠删分片以后存储在磁盘上,元数据以明文形式存放在元数据文件里(xl.json)。假定对象名字为obj-with-metadata, 它所在的桶的名字是bucket_name, disk是该对象所在纠删组的任一个磁盘的路径,如下目录:
disk/bucket_name/obj-with-metadata ,记录了这个对象在此磁盘上的信息。其中的内容如下:
其中的xl.json即是此对象的元数据文件。part.1 即此对象的第一个数据分片。对象的元数据文件xl.json的内容是如下这种形式的json字符串
字段说明
1、format字段
该字段指明了这个对象的格式是xl。MinIO内部存储数据主要有两种数据格式:xl与fs。使用如下命令启动的MinIO使用的存储格式是fs:
这种模式主要用于测试, 对象存储很多API都是并没有真正实现的桩函数。在生产环境所用的部署方式(本地分布式集群部署、联盟模式部署、云网关模式部署)中,存储格式都是xl。
2、stat字段
记录了此对象的状态,包括大小与修改时间,
3、erasure字段
这个字段记录此对象与纠删码有关的信息
其中的algorithm指明了此对象采用的是Klaus Post实现的纠删码,生成矩阵是范德蒙矩阵。
data,parity指明了纠删组中数据盘、校验盘的个数。
blockSize 指明了对象被分块的大小,默认是5M(请参见上一节“数据分布与均衡”)。
index指明了当前磁盘在纠删组中的序号。
distribution:每个纠删组的数据盘、校验盘的个数是固定的,但是不同的对象的分片写入这个纠删组的不同磁盘的顺序是不同的。这里记录了分布顺序。
checksum:它下面的字段个数跟此对象的分片数量有关。在旧版本的MinIO对象存储系统,每一个分片经过hash函数计算出的checksum会记录在元数据文件的这个位置。最新版的MinIO会把checksum直接计入分片文件(即part.1等文件)的前32个字节。
此字段之下algorithm的值是”highwayhash256S”表明checksum值是写入分片文件的。
4、minio字段
这个字段记录了存储此对象的minio的版本。
5、meta字段
Content-type, etag两个字段是MinIO对象存储系统自动生成的。
用户在使用Python等语言的写作的访问MinIO的程序中,如果上传对象时候指定了几个自定义属性,比如:
author属性值为Zhangsan
Nation属性值为Cn
Type属性值为love
那么对象元数据文件的meta字段就会出现如下几个子字段:
X-Amz-Meta-Author
X-Amz-Meta-Nation
X-Amz-Meta-Type
04、集群扩展
1)、扩展方式
MinIO支持联盟部署模式,即将多个MinIO集群组成一个统一命名空间(一个ETCD集群,若干个CoreDNS服务)。其中ETCD集群相当于整个对象存储系统的配置数据库,很多重要的信息,如桶IP地址等存储于其中。
联盟模式多集群部署
同样,MinIO在扩展时也采用相同机制,而不是传统分布式存储的添加节点方式。MinIO主要通过添加新的集群来扩大整个系统,可用空间大幅增加且仍然保持统一命名空间。通过这种方式,MinIO对象存储系统几乎可以无限的扩展总体性能和容量。
2)、统一域名访问
MinIO集群扩展加入新了集群或桶后,对象存储的客户端程序需要通过统一域名/url(如bucket1. domain .com)来访问数据对象,这个过程涉及到了CoreDNS系统。
CoreDNS实现单一域名/URL访问
MinIO对象存储的某个客户端(比如mc),首先向某个MinIO服务发送创建桶的请求。MinIO服务把这个桶所在的MinIO集群的外部网址(一般为一个Nginx的IP地址,或者MinIO集群的每一台服务器的IP地址),写入到etcd集群中。
假定域名为domain.com,桶名为buc-1,集群的服务器IP地址为192.168.1.108、192.168.1.109,那么写入etcd集群的共有两条数据.第一条数据的key,value二元组
第二条数据的key,value二元组
CoreDNS通过etcd系统获知”bucket1. domain .com”这个url所对应的两个IP地址为192.168.1.108, 192.168.1.109。对象存储的客户端主机设置如上所配置的CoreDNS服务之后,客户端程序就可以通过域名”bucket1.domain.com”来找到访问这个桶。
3)、优势特性
单一的、超大的命名空间需要花费大量的创建、维护与停机时间,复杂的部署管理,进而带来更严重的次生故障。MinIO的设计理念就是化整为零,简化集群扩展,减小单个集群的体量,轻量化单个集群的运维,从而使得超大规模的存储管理与维护变得更加容易。
集群的节点完全对等,没有主节点,多个节点可以并发提供对象访问服务;
创建桶的时候,可以指定数据中心/地域,以匹配对应的业务访问;
无论添加多少个集群,原有集群的性能几乎是不变的;
集群不会过大(32个节点),可实现可靠的分布式锁管理器,进而保证更新、删除等操作的强一致性。传统的架构允许集群扩容到数百上千节点,此情况下的强一致性容易产生性能问题;
故障的影响范围小,限制在单个集群内部。
05、纠删码
在同一集群内,MinIO会自动生成若干纠删组,用于存放桶数据。一个纠删组中的一定数量的磁盘发生的故障(故障磁盘的数量小于等于校验盘的数量),通过纠删码算法可以恢复出正确的数据。
MinIO集成了Reed-Solomon纠删码库,MinIO存储对象数据时,首先把它生成若干等长的片段(对于大对象,默认按5MB切片),然后每一个片段会纠删算法分成若干分片,包括数据分片与校验分片,每个分片放置在一个纠删组的某个节点上。对象的每一个数据分片、校验分片都被“防比特位衰减”算法所保护。
对于一个对象,MinIO是如何定位它所在的纠删组呢?
假定所有的纠删组都有一个序号(从0开始,直至纠删组个数减1)。MinIO会根据对象名(类似于文件系统的全路径名),使用crc32哈希算法计算出一个整数。然后使用这个整数除以纠删组的个数,得到一个余数。这个余数,可以作为纠删组的序号,这样就确定了这个对象所在的纠删组。
MinIO采用CRC32哈希算法,与GlusterFs的Davies-Meyer哈希算法(性能、冲突概率与md4, md5相近)不一样的是, CRC32算法的哈希值分布较不均匀,但运算速度极快,高出md4数倍。相对于容量均衡,MinIO更看重数据的写入速度。
06、数据修复
比特位衰减(Bitrot)是指存在存储介质中的数据发生了缓慢的变化,如向存储介质写入一段比特流,一段时间后再读出来,二者并不一致。比特位衰减的原因大致有:磁记录磨损、磁盘幻象写(phantom writes)、磁盘指向错误(misdirectedreads/writes)、宇宙射线的辐射等。MinIO对象存储系统从设计之初即考虑到修复静默错误,从被修复的目标来说,按照大小可以分为以下三种类型的修复:某个对象、某个桶、整个集群。
在控制台上执行mc命令即开始进行数据修复。该命令一方面向minio发送数据修复的HTTP请求,另一方面不断地接收minio服务进程返回的修复进度信息,而后输出到控制台,直到修复工作完毕。
如前文所述,每个对象都被分成多个分片,然后存储于多台主机的磁盘上。数据修复可以分为正常、深度两种模式,正常模式下只是简单地检查分片状态信息,深度模式下会使用hash算法来校验分片的内容,找出比特位错误,同时也更耗费资源。
MinIO具体修复流程如下:
mc命令作为MinIO对象存储的客户端软件、管理工具,它内部链接了minio软件(代码网址:https://github.com/minio/minio/)的madmin软件模块,通过调用madmin中的修复函数,mc包装了mc命令的命令行参数,然后向minio服务进程发送HTTP消息。
mc发送一个修复请求,在minio中被类healSequence所描述。每一个healSequence可以启动、停止、查询状态。minio服务程序收到新的任务的时候,会检查是否跟原有的healSequence有重叠的任务,如果有重叠,则启动的修复任务失败。如果minio服务没有发现错误,则使用深度优先搜索的算法,按照磁盘元数据信息、桶、对象的顺序,不断地给后台修复线程推送任务。
minio后台修复线程修复对象的流程算法:对于对象的每一个block(默认大小为5M),从纠删组的各个主机读取各个分片,如果有错误的分片,就需要修复,有两种可能:校验分片错误——minio使用各个数据分片重新计算缺失的校验片。数据分片错误——使用纠删算法恢复数据(需要计算逆矩阵)。
07、lambda计算
MinIO对象存储软件支持lambda计算通知机制,即桶中的对象支持事件通知机制。MinIO当前支持的事件类型有:对象上传、对象下载、对象删除、对象复制等。MinIO对象存储软件当前支持的事件接受系统有:Redis,NATS, AMQP, MQTT,Apache Kafka, MySql, PostgreSQL, Elasticsearch等。
对象通知机制,极大地增强了MinIO对象存储的扩展性,可以让用户通过自行开发来实现某些MinIO对象存储不便实现的功能,比如基于元数据进行的各种检索、各种跟用户的业务有关的计算。既方便了用户,又有助于MinIO对象存储的生态建设。
对象通知机制,使用极为简单,用户只需在MinIO进行少许配置即可。请参考文献[15]。
08、持续备份
传统的复制的一大问题是不能有效地扩展,很难超过几百TB。在当今的时代,为了支持灾难恢复,任何单位都需要一个备份策略。而且这个备份策略需要跨越不同的地理位置、不同的数据中心、多种多样的云环境。
MinIO的持续备份是为跨数据中心的大型部署而设计的。通过使用lambda计算通知机制,它可以快速、有效地计算处需要增量备份的内容,这远比传统的批处理模式优秀。持续备份使得未备份的数据尽可能的少,这意味着发生灾难或者严重错误时候,丢失的数据尽可能的少,很好地保护了用户的数据资产。
09、软件模块
MinIO对象存储系统主要由以下软件模块部分组成:存储服务器软件minio,存储客户端软件mc,多种语言的客户端SDK。minio分为上下两层,上层负责minio的系统管理与对外接口,下层实现具体的逻辑。
1)、cmd模块
这是minio的上层,也就是源代码中的cmd子目录,这一部分主要负责minio的命令行参数解析、初始化系统、格式化磁盘、管理内嵌的web服务器、S3 API的解析与逻辑处理。
2)、各个软件包
这个是minio底层逻辑实现,也就是源代码目录中的pkg子目录。其中一些软件包(比如madmin), 可被其它组织(或个人)在编写辅助minio的软件的时候所重复使用。
madmin:使用这个软件包可以自己使用Golang语言撰写MinIO集群的管理程序,比如获取服务的状态(磁盘、cpu等信息)、重启某个机器服务、启动修复某个桶的任务、重新配置系统、获取剖析信息等等。
S3 select:如果对象存储系统中有很多超大型的对象,比如大小是几个GB甚至几个TB的对象。如果应用程序(比如spark分析程序),要把符合条件的若干个对象都读过去,然后再做分析,会及其的慢,浪费很多带宽(毕竟对象中可能只有很少的一部分是对某个分析程序有用的)。因此Amazon引入了S3 Select 的功能。通俗地说,就是把select 类型的sql语句在某个对象上执行,从对象中取出一部分内容返回给应用。MinIO提供了S3 Select 功能。相对于S3 Select, MinIO要求对象的内容必须是CSV、 JSON,或者 Parquet格式。S3Select API实现中使用的语法分析器是 Alec Thomas写的如下项目:
https://github.com/alecthomas/participle
这个实现的分析算法是带有栈的ll(k)分析算法。
三、性能测试
MinIO已经为高性能做过高度优化,尤其是部分关键的算法已经使用SIMD指令对Intel(AVX2/AVX512)、Arm(NEON)的cpu做过特殊优化,主要包括:
1) 纠删码部分用到的伽罗瓦域的运算:加法、乘法、乘方等等;
2) 监测比特位衰减(bitrot)的哈希函数,如HighwayHash。
另外每一个MinIO集群都是无中心的,其中的每一个节点都是对等的,从而在性能上,不会存在单点瓶颈,也不会有单点故障。
四、设计讨论
•传统的扩展方式的劣势
通过增加节点来扩展单集群,一般需要进行数据均衡,否则群集内各存储节点会因负载不均而出现新的瓶颈。除了数据均衡操作的时机这个问题以外,在均衡过程中一般需要从存储使用率高的节点向使用率低的节点迁移数据。
当集群扩容之后,大量已经写入的文件落点会出现改变,文件需要迁移到真实的落点。当存储系统容量比较大时,则会发生大量的文件/对象进行迁移,迁移过程可能由于占用大量资源而导致上层应用性能下降。而且当文件/对象迁移过程中,机器故障可能会导致一些意想不到的情况,尤其是有大量业务的时候。当然针对此类问题,Gluterfs之类的文件系统有一些比较复杂的处理办法。
•使用场景
人工智能、大数据分析、视频监控等典型使用场景中,对象存储系统中存储的数据往往写入以后一般不再修改。如果现有MinIO集群存储空间使用完毕,重新添加新集群,然后继续写入新集群即可。MinIO对象存储的客户端应用,从业务层面自行决定那些对象存在于哪个集群里面,使用起来并不麻烦。
单集群不可扩展,也就是说系统不需要处理扩展和数据均衡,不仅有效降低系统复杂性,而且可以使得系统部署规划具有很好的可预测性。
对于海量对象存储应用场景,数据通常具有典型的生命周期特征,根据实际需求设计好单集群规模,按联合方式扩展,整个系统具有非常好的可维护性。
•MinIO方案的优势
不支持对单个集群进行扩展,MinIO对象存储系统的这种设计,使得系统的很多模块更加简单(比如从一个对象转换到它所在的纠删组,只用简单的哈希即可。)降低了整个系统出错的概率,使得MinIO对象存储系统更加可靠、稳定。
MinIO是否有类似于GlusterFs 的translator类机制?没有,GlusterFs是使用c语言实现的,而c语言是比较低级的语言,本身没有模块机制。Golang语言自身有强大的模块机制,所以也就不需要类似于translator之类的机制。
MinIO的纠删码机制,为何没有采用柯西矩阵?就Reed-Solomon纠删码的生成矩阵来说,Klaus的纠删码库里面可以选择柯西生成矩阵。不过当前MinIO软件使用的仍然是范德蒙矩阵的Reed-Solomon纠删算法。这是因为:虽然柯西矩阵的生成相比范德蒙矩阵更快,不过MinIO编码矩阵的生成是只进行一次的操作(程序运行中,生成的这个矩阵会被保存起来)。使用柯西矩阵对数据的吞吐量并没有什么影响。
五、对象存储产品选型讨论
开源对象存储软件以MinIO,Ceph为典型代表。为帮助相关人员在选择对象存储系统之时选择合适的产品,此处对二者的特点、特性做一定讨论。
01、MinIO优势
1、部署极其简单
MinIO系统的服务程序仅有minio一个可执行文件,基本不依赖其它共享库或者rpm/apt包。minio的配置项很少(大部分都是内核之类系统级的设置),甚至不配置也可以正常运行起来。百度、google、bing等搜索引擎上基本没有关于MinIO部署问题的网页,可见在实践中,很少有使用者遇到这方面的问题。
相比之下,Ceph系统的模块,相关的rpm、apt包众多,配置项非常多,难以部署,难调优。某些Linux发行版的Ceph安装包甚至有bug,需要使用者手动改动Ceph的python脚本,才能安装完毕。
2、二次开发容易
MinIO对象存储系统除了极少数代码使用汇编实现以外,全部使用Golang语言实现。Ceph系统是使用业界闻名的难学难用的c++语言编写的。Golang语言由于产生较晚,吸收了很多语言尤其是c++的教训,语言特性比较现代化。相对而言,MinIO系统的维护、二次开发比较容易。
3、网管模式支持多种其他存储
通过网关模式,MinIO对象存储后端,可以对接各种现有的常见其它存储类型,比如的NAS系统,微软Azure Blob 存储、Google 云存储、HDFS、阿里巴巴OSS、亚马逊S3等,非常有利于企业复用现有资源,有利于企业低成本(硬件成本约等于零,部署MinIO对象存储软件即可)地从现有系统平滑升级到对象存储。
02、Ceph优势
•数据冗余策略更加丰富
Ceph同时支持副本、纠删码,而MinIO只支持纠删码。对于个别的对于数据可靠性要求极高的单位,Ceph对象存储更加合适。
•社区目前更成熟
03、其他对比
1)、厂商支持
国内使用Ceph的厂商、基于Ceph进行自研的存储厂商都比较多,在使用过程中遇到的问题(有些时候,甚至需要修改、增强乃至重新实现Ceph本身的功能),可以向相关厂商寻求支持。国际方面,Ceph早已被红帽收购,而红帽近期又被IBM收购。
MinIO开发与支持的厂商只有MinIO公司。由于架构比较先进,语言高级,MinIO本身的程序比较容易读懂、修改。招聘Golang程序员来 维护MinIO所花费的成本,显然低于招聘c++程序员来维护Ceph。
2)、多语言客户端SDK
二者均有常见编程语言的客户端,比如:python, java等。MinIO对象存储软件的开发SDK另外支持纯函数式的语言Haskell。
3)、技术文档
内部实现的文档MinIO基本不存在。想要了解内部实现乃至参与开发的技术人员,只能到如下社区:
04、结论
由以上讨论,可见作为对象存储软件来说,MinIO, Ceph都非常优秀,各自有各自的优势。准备使用对象存储软件的用户,应该根据自己单位的需求、技术储备等实际情况,选择适当的软件。
http://www.cloudbin.cn/?p=2917
http://www.minio.org.cn/
http://docs.minio.org.cn/docs/
http://t.zoukankan.com/zengpeng-p-14134667.html
抛去一些文档和其他辅助的文件,主要代码都是在根目录的 cmd pkg 两个文件夹中,main.go 是代码函数入口
├── CONTRIBUTING.md
├── CREDITS
├── Dockerfile
├── Dockerfile.cicd
├── Dockerfile.dev
├── Dockerfile.dev.browser
├── Dockerfile.mint
├── Dockerfile.release
├── LICENSE
├── Makefile
├── NOTICE
├── README.md
├── README_zh_CN.md
├── SECURITY.md
├── VULNERABILITY_REPORT.md
├── browser
├── buildscripts
├── cmd —主流程代码
├── code_of_conduct.md
├── config
├── docker-buildx.sh
├── dockerscripts
├── docs
├── gateway
├── go.mod
├── go.sum
├── main.go —主函数入口
├── minio
├── minio.iml
├── minio.spec
├── minio_server
├── mint
├── pkg —依赖包
├── resources
├── ruleguard.rules.go
└── staticcheck.conf
代码主进程是从根目录的main.go的主函数进入,然后分为 gateway 和 server 两种模式,其中前者根据挂载的磁盘数量来决定是单机模式还是纠偏码模式。后者根据 gateway 后面的命令参数来决定是使用什么代理模式进行,目前支持 azure gcs hdfs nas s3。
2.3.1. server初始化
server 模式下,无论是 fs 单磁盘模式,还是 Erasure 模式,都会经历以下步骤(包含在cmd/server-main.go 文件中)
验证认证信息是否是默认认证信息,如果是,提示修改
2.3.2. gateway初始化
gateway 模式下,无论后面代理的是哪种类型的存储(azure gcs hdfs nas s3),都会经历以下步骤(包含在cmd/gateway-main.go 文件中),从下面的步骤可以看出,和 server 模式区别不是很大
2.4.1. globalAllHealState(cmd/admin-heal-ops.go)
所有等待修复bucket的状态机,在内存中保存,主要是针对客户端请求的,状态分为以下几种
healNotStartedStatus
healRunningStatus
healStoppedStatus
healFinishedStatus
2.4.2. globalBackgroundHealState(cmd/admin-heal-ops.go)
所有为自动化恢复bucket的状态机,在内存中保存,状态分为以下几种
healNotStartedStatus
healRunningStatus
healStoppedStatus
healFinishedStatus
2.4.3. globalNotificationSys(cmd/notification.go)
初始化: 在bucket通知可以被支持的情况下,载入所有的 EndpointServer
,然后绑定 bucket
, ObjectLayer
使用: 在各个api尾部调用 send
接口,然后根据 eventName
和 objectName
拉出所有的通知target,然后发送通知
其他: 提供了一个接口 GetBandwidthReports
用来获取一组bucket的带宽,通过调用各个客户端的 /bandwidth
接口获取
2.4.4. globalBucketMetadataSys(cmd/bucket-metadata-sys.go)
初始化: 在bucket通知可以被支持的情况下,绑定 bucket
, ObjectLayer
使用: 所有关于bucket的meta信息都保存在 /buckets/{bucketName}/.metadata.bin
的object中,meta信息内容可以查看以下结构体,调用 getObject
接口获取对应bucket的meta信息
其他: 载入bucket信息的时候是并发载入的,每次100个,接口为 concurrentLoad
type BucketMetadata struct {
Name string
Created time.Time
LockEnabled bool // legacy not used anymore.
PolicyConfigJSON []byte
NotificationConfigXML []byte
LifecycleConfigXML []byte
ObjectLockConfigXML []byte
VersioningConfigXML []byte
EncryptionConfigXML []byte
TaggingConfigXML []byte
QuotaConfigJSON []byte
ReplicationConfigXML []byte
BucketTargetsConfigJSON []byte
BucketTargetsConfigMetaJSON []byte
// Unexported fields. Must be updated atomically.
policyConfig *policy.Policy
notificationConfig *event.Config
lifecycleConfig *lifecycle.Lifecycle
objectLockConfig *objectlock.Config
versioningConfig *versioning.Versioning
sseConfig *bucketsse.BucketSSEConfig
taggingConfig *tags.Tags
quotaConfig *madmin.BucketQuota
replicationConfig *replication.Config
bucketTargetConfig *madmin.BucketTargets
bucketTargetConfigMeta map[string]string } 2.4.5. globalBucketMonitor(cmd/config.go) 初始化: 传入一个 `chan`,初始化对象,无外部其他依赖。 使用: 通过包装包含 `throttleBandwidth` 接口的reader `NewMonitoredReader`,从而在执行 `replicateObject` 时进行统计,并通过 `GetReport` ` 接口获取 2.4.6. globalConfigSys 初始化: 绑定 `ObjectLayer`,然后按照下面优先级进行合并
1. 命令行指定的配置文件位置 `{config_location}/minioConfigFile`
2. home目录下 `${HOME}/.minio/config.json`
3. 数据盘目录下 `<export_path>/.minio.sys/config/config.json`
4. 读取object `/.minio.sys/config/config.json`
使用: 通过增删改接口查获取配置
2.4.7. globalIAMSys(cmd/iam.go)
初始化: 绑定 ObjectLayer
,先拿到 /.minio.sys/config/iam.lock
的锁,然后查看 globalEtcdClient
是否为空, 如果不为空再进行合并
使用: 通过 IAMSys
提供的接口进行查询 user
group
policy
信息,然后再各个api接口中判断认证和权限
其他:
1. 通过 IAMStorageAPI
这个interface,来抽象IAM相关信息的存储,目前实现有以下两种
1. `IAMEtcdStore`(cmd/iam-etcd-store.go)
2. `IAMObjectStore`(cmd/IAMObjectStore.go)
1. user和group是多对多的关系
2. user 和 group 都可以拥有policy IAM IAM 2.4.8. globalPolicySys 初始化: 构造空结构体 使用: 通过查询bucket的config信息来获取对应的配置 2.4.9. globalLifecycleSys 初始化: 构造空结构体 使用: 通过查询bucket的config信息来获取对应的配置 2.4.10. globalBucketSSEConfigSys 初始化: 构造空结构体 使用: 通过查询bucket的config信息来获取对应的配置 2.4.11. globalBucketObjectLockSys 初始化: 构造空结构体 使用: 通过查询object的meta信息来获取对应配置 2.4.12. globalBucketQuotaSys 初始化: 构造空结构体 使用: 通过查询bucket的config信息来获取对应的配置 2.4.13. globalBucketVersioningSys 初始化: 构造空结构体 使用: 通过查询bucket的config信息来获取对应的配置 2.4.14. globalBucketTargetSys 初始化: 构造空结构体 使用: 通过查询bucket的config信息来获取对应的配置 2.5. hook 介绍 纵观 minio 所有源码,hook部分是一大亮点,考虑了很多,分别从以下几个方面进行了考虑
系统层面 setMaxResources(这个不属于hook)
代码层面 setReservedBucketHandler setBrowserCacheControlHandler criticalErrorHandler
web层面
setRedirectHandler
addCustomHeaders
setBucketForwardingHandler
setBrowserRedirectHandler
安全层面
请求域名
setCrossDomainPolicy
请求头
corsHandler
addSecurityHeaders
setRequestValidityHandler
setRequestHeaderSizeLimitHandler
setSSETLSHandler
请求体
setRequestSizeLimitHandler
url
setIgnoreResourcesHandler
setAuthHandler
自定义
filterReservedMetadata
审计层面
setHTTPStatsHandler
2.6. api层介绍
api层调用层级结构如下图,从图中我们可以看出,
无论是 gateway 还是 server 模式都是通过实现 ObjectAPI 这个interface来进行服务
在 objectAPIHandlers 这一层面,主要是做了一些检查,实际针对内容处理是放在ObjectAPI 这个interface的实现层,以 putObject 为例,做了以下内容
检查 http 头字段
验证签名
bucket容量检查
验证md5
API
API
2.7. 对象放入和获取流程(仅仅针对分布式模式)
以下分析以 server 模式下, ObjectAPI 的纠偏码模式实现来进行分析
2.7.1. PutObject(cmd/erasure-sets.go)
根据 object 的名称hash值获取磁盘Set集合。
(cmd/erasure-object.go) 调用 putObject 接口
根据设置的 StorageClass 和磁盘set磁盘总数算出奇偶校验驱动器所使用驱动器数量和数据盘所使用驱动器数量
根据算出来的奇偶校验驱动器驱动器数量排序,把set集合中的驱动器分好
根据算出的奇偶校验块和数据盘块初始化 erasure 对象,并根据磁盘和数据包初始化好各个驱动器的 BitrotWriter,注意是先写到临时目录(.minio.sys/tmp/{uniqueID}/{DataDir}/{partName})
每次读取一定量的数据,进行并发写。实际最后是调用到了 StorageRESTClient.AppendFile 接口
全部数据写完后,调用 renameData 接口移动文件夹到真正的文件路径,实际最后是调用到了 StorageRESTClient.RenameData 接口
删除临时文件
备注:newErasureServerPools->waitForFormatErasure->connectLoadInitFormats->initStorageDisksWithErrors->newStorageAPI->newStorageRESTClient 来进行初始化 StorageApi
2.7.2. GetObject(erasure-sets.go)
此接口步骤基本上和PutObject相同,只不过接口换成了 StorageRESTClient.ReadFile,多了一个heal的步骤
https://fengmeng.xyz/p/go-minio/
https://blog.openacid.com/storage/ec-1/
分布式系统的第一个问题是可靠
解决了数据可靠性的问题之后, 数据的其他问题如一致性, 性能, 可用性等的讨论才有意义.
而提高可靠性最直接最简单的方法, 就是 对一份数据存储多个副本 (副本数一般选择3).
结合目前经验上的磁盘的损坏率(大约是年损坏率7%), 3个副本可以达到一个工业可接受的可靠性, 这个可靠性的预期大约是11个9以上(99.999999999%的概率不丢数据).
有些时候为了降低成本, 只存储2个副本, 也可以达到8个9的可靠性.
3副本的方式虽然简单容易实现, 但要额外浪费2倍的存储空间, 因此存储领域中一直都希望用一种较少的冗余的存储方式, 来实现同样较高的可靠性.
不论是单机上的RAID技术, 还是今天要提到的EC(Erasure-Code, 擦除码, 纠删码) 都是用来解决这个问题的. 接下来, 我们通过几个例子, 来逐步展示 EC 的工作原理.
RAID 本质上跟EC没有区别, 它是单机系统时代被广泛使用的成熟实现. EC可以认为是分布式系统发展起来后, RAID算法在多机系统上的重新实现:
RAID-0 相当于单副本;
RAID-1 相当于2副本;
RAID-5 相当于EC的k+1模式, k个数据块+1个校验块;
RAID-6 相当于EC的k+2模式, k个数据块+2个校验块;
EC的基本原理Permalink
EC的目标可以简单的理解为: 对k个同样大小的数据块, 额外增加m个校验块, 以使得这k+m个数据中任意丢失m个数据块/校验块时都能把丢失的数据找回.
Q: 有3个自然数, 能否做到再记录第4个数字, 让任何一个数字丢失的时候都可以将其找回?
这个问题很简单, 记录这3个数字的和: 假设3个数字是: d₁, d₂, d₃ ; 再存储一个 y₁ = d₁ + d₂ + d₃ 就可以了.
于是:
存储过程:
就是存储这4个数字: d₁, d₂, d₃, y₁.
恢复过程:
如果 d₁, d₂, d₃ 任意一个丢失, 例如 d₁ 丢失了, 我们都可以通过 d₁ = y₁ - d₂ - d₃ 来得到 d₁ .
如果 y₁ 丢失, 则再次取 d₁ + d₂ + d₃ 的和就可以将 y₁ 找回.
这种求和冗余策略, 就是 EC 算法的核心.
在上面这个简单的系统中, 总共存储了4份数据, 有效的数据是3份. 冗余是133%, 它的可靠性和2副本的200%冗余的存储策略差不多: 最多允许丢失1份数据.
实现k+2的冗余策略Permalink
有3块数据: d₁, d₂, d₃ 可否另外再存储2个冗余的校验块(共5块), 让整个系统任意丢失2份数据时都能找回?
在k+1求和的策略里, 我们给数据块和校验块建立了一个方程, 把它们关联起来了: y₁ = d₁ + d₂ + d₃.
现在, 如果要增加可丢失的块数, 简单的把 y₁ 存2次是不够的.
例如我们存储了2个校验块:
[\begin{cases} d_1 + d_2 + d_3 = y_1 \ d_1 + d_2 + d_3 = y_2 \end{cases}]
存储过程:
存储 d₁, d₂, d₃, y₁, y₂ 这5个数字.
恢复过程:
如果 d₁, d₂ 都丢失了(用 u₁, u₂ 表示丢失的数据), 下面这个关于 u₁, u₂ 的线性方程是有无穷多解的:
[\begin{cases} u_1 + u_2 = y_1 - d_3 \ u_1 + u_2 = y_2 - d_3 \end{cases}]
我们没有办法从这个方程组里解出 u₁, u₂ 的值, 因为第2个方程跟第1个一毛一样, 没有提供更多的信息.
所以我们现在需要做的是, 对第2个校验块 y₂, 设计一个新的计算方法, 使之跟3个数据块之间建立一个不同的关联, 使得当 d₁, d₂ 丢失时方程组有解:
我们采用的方式是, 在计算 y₂ 时, 给每个数据 dⱼ 设置不同的系数:
计算 y₁ 时, 对每个数字乘以1, 1, 1, 1 …
计算 y₂ 时, 对每个数字乘以1, 2, 4, 8 …
[\begin{aligned} y_1 & = d_1 + d_2 + d_3 \ y_2 & = d_1 + 2 d_2 + 4 d_3 \end{aligned}]
按照此方案, 我们就可以建议一个k+2的存储系统:
存储过程:
存储 d₁, d₂, d₃, y₁, y₂ 这5个数字.
数据恢复:
如果 d₁ 或 d₂ 之一丢失,恢复的过程跟k+1策略一样;
如果 d₁, d₂ 丢失(同样用 u₁, u₂ 表示), 我们可以使用剩下的3个数字 d₃, y₁, y₂ 来建里1个关于 u₁, u₂ 的二元一次方程组:
[\begin{cases} \begin{aligned} u_1 + u_2 & = y_1 - d_3 \ u_1 + 2 u_2 & = y_2 - 4 d_3 \end{aligned} \end{cases}]
解出上面这个方程组, 就找回了丢失的 u₁, u₂ .
以上这种加系数计算校验块的方式, 就是RAID-6的基本工作方式:
RAID-6为k个数据块(例如k=10)之外再多存储2个校验数据, 当整个系统丢失2块数据时, 都可以找回.
为什么计算 y₂ 的系数是1, 2, 4, 8…? 系数的选择有很多种方法, 1, 2, 4, 8是其中一个. 只要保证最终丢失2个数字构成的方程组有唯一解就可以. 在k+2的场景中, 选择1, 2, 3, 4…作为系数也可以.
到这里我们就得到了k+2的EC的算法: 通过166%的冗余, 实现差不多和三副本300%冗余一样的可靠性.
这样我们通过不断的增加不同的系数, 就可以得到任意的k+m的EC冗余存储策略的实现.
到此为止, 就是EC算法的核心思想了. 接下来, 我们再深入1点, 从另外1个角度来解释下为什么要选择这样1组系数.
现实中使用的RAID-5和RAID-6都是 EC 算法的子集. EC 是更具通用性的算法. 但因为实现的成本(主要是恢复数据时的计算开销), RAID-5 和 RAID-6在单机的可靠性实现中还是占主流地位.
但随着存储量的不断增大, 百PB的存储已经不算是很极端场景了. RAID-6 在单机环境下不算高的数据丢失风险在大数据量的场景中显示的越来越明显. 于是在云存储(大规模存储)领域, 能支持更多的冗余校验块的EC成为了主流.
EC的解码: 求解n元一次方程组Permalink
EC生成校验块的过程称之为EC的编码, 也就是用Vandermonde矩阵去乘所有的数据块.
而当数据丢失需要找回的时候, 使用的是EC的解码过程.
既然EC的编码过程是编码矩阵(Vandermonde)和数据块列相乘:
http://blog.codeg.cn/post/blog/2016-09-06-minio-source-code-reading/
HTTP事件注册
启动阶段的初始化工作还是相当繁琐,没戏看。重点看一下运行期间的功能。
minio进程起来了,对外提供HTTP服务,那么找到HTTP的事件注册的函数就是最好的入口点。事件处理函数的注册代码路径为:serverMain -> configureServerHandler -> api-router.go:registerAPIRouter
在registerAPIRouter这个函数中,注册了所有HTTP相关的事件处理回调函数。事件分发使用了github.com/gorilla/mux库。这个mux库,在Golang的项目中,使用率还是蛮多的,上次我在Trafix项目中也看到是使用mux库来处理HTTP事件注册和分发处理。
PutObject:上传一个对象
注册回调函数为bucket.Methods(“PUT”).Path(“/{object:.+}”).HandlerFunc(api.PutObjectHandler)
下面我们来分析一下func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Request)函数的实现。
首先,检测HTTP HEADER中是否有设置 X-Amz-Copy-Source
检测HTTP HEADER中的Content-Md5,并获取该MD5(注意:该MD5是16进制数Base64Encode之后的结果)
检测是否有相应权限
检测是否超过最大大小限制
根据权限,调用相应的函数。这里我们重点介绍api.ObjectAPI.PutObject(bucket, object, size, r.Body, metadata)
如果是单机版本,会进入func (fs fsObjects) PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (string, error)函数中
继续分析func (fs fsObjects) PutObject(…)函数
首先检测 BucketName、ObjectName 是否合法
生成一个UUID,然后根据UUID生成一个唯一的临时的obj路径tempObj
new一个MD5对象,用来计算上传的数据的MD5
根据HTTP请求的Reader生成一个io.TeeReader对象,用来读取数据的同时,顺便计算一下MD5值
调用func fsCreateFile(…)来创建一个临时的对象文件
再检查计算出来的MD5是否与HTTP HEADER中的MD5完全一致
如果MD5不一致就删除临时文件,返回错误。如果MD5完全一致,就将临时文件Rename为目标文件
最后,如果HTTP HEADER中有额外的meta数据需要写入,就调用writeFSMetadata写入meta文件中
最最后,返回数据的MD5值
上面func fsCreateFile(…)中,会调用disk.AppendFile(…)来创建文件。如果是单机版,这个函数的具体实现为func (s *posix) AppendFile(volume, path string, buf []byte) (err error)。
GetObject:查询一个对象
注册回调函数为bucket.Methods(“GET”).Path(“/{object:.+}”).HandlerFunc(api.GetObjectHandler),该函数分析如下:
从URL中获取 bucket 、 object 的具体值
检测是否有权限
查询ObjectInfo数据
检测HTTP HEADER看看,是否HTTP Range查询模式(也就是说minio支持断点续传)
调用api.ObjectAPI.GetObject来获取对象数据
如果是单机版,会进入func (fs fsObjects) GetObject(bucket, object string, offset int64, length int64, writer io.Writer) (err error)函数中
继续分析func (fs fsObjects) GetObject(…)函数
首先检测 BucketName、ObjectName 是否合法
继续检测其他参数是否合法,例如 offset、length 等
调用fs.storage.StatFile接口来获取对象文件的长度信息,并与请求参数做对比,核验是否合法
调用fs.storage.ReadFile来获取文件数据
https://codeleading.com/tag/MinIO%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/
https://codeleading.com/article/98131696806/
1、扩展方式
MinIO支持联盟部署模式,即将多个MinIO集群组成一个统一命名空间(一个ETCD集群,若干个CoreDNS服务)。其中ETCD集群相当于整个对象存储系统的配置数据库,很多重要的信息,如桶IP地址等存储于其中。这种模式的MinIO的架构如下图:
分享图片
联盟模式多集群部署
同样,MinIO在扩展时也采用相同机制,而不是传统分布式存储的添加节点方式。MinIO主要通过添加新的集群来扩大整个系统,可用空间大幅增加且仍然保持统一命名空间。通过这种方式,MinIO对象存储系统几乎可以无限的扩展总体性能和容量。
2、统一域名访问
MinIO集群扩展加入新了集群或桶后,对象存储的客户端程序需要通过统一域名/url(如bucket1. domain .com)来访问数据对象,这个过程涉及到了CoreDNS系统。
分享图片
CoreDNS实现单一域名/URL访问
MinIO对象存储的某个客户端(比如mc),首先向某个MinIO服务发送创建桶的请求。MinIO服务把这个桶所在的MinIO集群的外部网址(一般为一个Nginx的IP地址,或者MinIO集群的每一台服务器的IP地址),写入到etcd集群中。
假定域名为domain.com,桶名为buc-1,集群的服务器IP地址为192.168.1.108、192.168.1.109,那么写入etcd集群的共有两条数据.第一条数据的key,value二元组为:
第二条数据的key,value二元组为:
CoreDNS通过etcd系统获知”bucket1. domain .com”这个url所对应的两个IP地址为192.168.1.108, 192.168.1.109。对象存储的客户端主机设置如上所配置的CoreDNS服务之后,客户端程序就可以通过域名”bucket1.domain.com”来找到访问这个桶。
3、优势特性
单一的、超大的命名空间需要花费大量的创建、维护与停机时间,复杂的部署管理,进而带来更严重的次生故障。MinIO的设计理念就是化整为零,简化集群扩展,减小单个集群的体量,轻量化单个集群的运维,从而使得超大规模的存储管理与维护变得更加容易。
集群的节点完全对等,没有主节点,多个节点可以并发提供对象访问服务;
创建桶的时候,可以指定数据中心/地域,以匹配对应的业务访问;
无论添加多少个集群,原有集群的性能几乎是不变的;
集群不会过大(32个节点),可实现可靠的分布式锁管理器,进而保证更新、删除等操作的强一致性。传统的架构允许集群扩容到数百上千节点,此情况下的强一致性容易产生性能问题;
故障的影响范围小,限制在单个集群内部。
05、纠删码
在同一集群内,MinIO会自动生成若干纠删组,用于存放桶数据。一个纠删组中的一定数量的磁盘发生的故障(故障磁盘的数量小于等于校验盘的数量),通过纠删码算法可以恢复出正确的数据。
MinIO集成了Reed-Solomon纠删码库,MinIO存储对象数据时,首先把它生成若干等长的片段(对于大对象,默认按5MB切片),然后每一个片段会纠删算法分成若干分片,包括数据分片与校验分片,每个分片放置在一个纠删组的某个节点上。对象的每一个数据分片、校验分片都被“防比特位衰减”算法所保护。
https://www.debugger.wiki/article/html/1570628764059758
https://page.om.qq.com/page/OHm5iN1Er7_M3WScJYJhxJHQ0