MinIO 是一个高性能的对象存储服务器,用于构建云存储解决方案。它使用Golang编写,专为私有云、公有云和混合云环境设计。它是兼容Amazon S3 API的,并可以作为一个独立的存储后端或与其他流行的开源解决方案(如Kubernetes)集成。
1.部署方式
Minio 提供了两种部署方式:单机部署和分布式,两种部署方式都非常简单,其中分布式部署还提供了纠删码功能来降低数据丢失的风险。
当然如果我们只有一台机器,但是想用纠删码的功能,也可以直接配置使用多个本地盘
minio server /data1 /data2 /data3 ... /data8
2.基础概念
Object:存储到 Minio 的基本对象,如文件、字节流,Anything...
Bucket:用来存储 Object 的逻辑空间。每个 Bucket 之间的数据是相互隔离的。
Minio 的一些内部概念,以分布式部署为例:
Drive:部署 Minio 时设置的磁盘,Minio 中所有的对象数据都会存储在 Drive 里。
Set:一组 Drive 的集合,Minio 会自动根据 Drive 数量,将若干 Drive 划分为多个 Set(For example: {1...64} is divided into 4 sets each of size 16.)
3.上传对象
分布式模式下,上传文件的最终实现在 xlObjects.putObject() 中。xlObjects 就是前面提到的 Set,xlObjects.putObject()会将上传的文件存储到 Set 下的每个 Drive 中。下面我们就来分析下我们上传的文件,究竟是怎么上传到 Drive 下的。
2.1 划分 DataDrives & ParityDrives
文件上传的第一步,就是将 Set 下的所有 Drives 划分为 dataDrives 和 parityDrives。
DataDrives :纠删码中的数据盘,用来存储 Object 原始数据。
ParityDrives:纠删码中的冗余盘,用来存储 Object 计算出来的冗余数据。
Minio 纠删码的默认配置为 1 : 1,即数据盘和冗余盘个数相同,所以我们真正可用的存储空间,只有我们总空间的一半大小。
2.2 划分 Blocks
对于上传的 Object,Minio 会将其划分为多个 Block 来计算纠删码。
如果 Object Size > 10 MB,那么每个 block 的大小就是 10 MB。
如果 Object Size < 10 MB,如果也划分一个 10 MB 的 block 就太浪费空间了,所以分配一个大小刚好为 Object Size 的 block 就够了。
2.3 读取 Object 上传流
读取上传流逻辑在 Erasure.Encode()中Minio 会串行从上传流中读取数据,填到一个 Buffer 中,Buffer 的大小刚好为 2.2 确定的 Block 大小。
2.4 纠删码编码
每读取一块 Block,Minio 就会对其进行纠删码编码,生成 data shards 和 parity shards。 编码逻辑在Erasure.EncodeData()中。
因为一个 Set 中有多个 Drive(DataDrives + ParityDrives),所以 Minio 会先将一块 Block 按照 Drives 数量,划分为多个小块,这些小块在 Minio 中叫做 Shards。比如一个 Block 是 10MB, 而 Set 里有 16 个 Drive(8 DataDrives + 8 ParityDrives),此时 Minio 会将 Block 按照 10 MB / 8 DataDrives 的方式,将数据划分到 8 个 Data Shards,并额外再创建 8 个 空 Shards,用来存储编码后的冗余数据
接着 Minio 就会对 Data Shards 进行纠删码编码,并将编码后的冗余数据存储到前面创建的 8 个空 Shards 中,也就是 parity shards 中。
2.5 数据落盘
经过 2.4 的编码之后,我们得到了 Block 最终需要落盘的数据。
落盘的逻辑在 parallelWriter.Write()中。parallelWriter 看名字就知道,是并行 Writer。不过parallelWriter 本身并不直接写数据,它只是多个 writer 的一个并行封装。
for i := range p.writers {
if p.writers[i] == nil {
p.errs[i] = errDiskNotFound
continue
}
wg.Add(1)
go func(i int) {
defer wg.Done()
_, p.errs[i] = p.writers[i].Write(blocks[i])
if p.errs[i] != nil {
p.writers[i] = nil
}
}(i)
}
如上所示,parallelWriter 为每个 writer 分配了一个 block(注意,这里的 block 其实是上面所说的 shard),并为每个 writer 开启一个协程,并行写入数据。
那么这里的 writer 是什么呢?就是 minio 的每个磁盘的 writer。对于每块磁盘,minio 都会封装一个 writer 用来写入数据。
minio 除了用纠删码来保护数据,还采用了 Bitrot 技术。
Bitrot 是指位衰减,是指存储在存储介质中的数据的性能和完整性的缓慢恶化。它也被称为比特衰变、数据腐烂、数据衰变和静默数据损坏。
可以说纠删码是用来保证 Object 的每个 Block 的数据正确和可恢复的,而 Bitrot 技术是用来检验磁盘数据的正确性的。
纠删码技术比较复杂,但是 Bitrot 技术就比较简单了,具体逻辑在 streamingBitrotWriter 中。本质就是在写数据之前,先计算好数据的 hash,然后将 hash 先写入磁盘,再写入需要存储的数据。这样读取数据时,就可以通过重新计算 hash,和原始写入的 hash进行一致性比较,来判断数据是否有损坏。
上传文件时,Minio 不是直接上传到 object 在磁盘的最终存储目录的,而是先写到一个临时目录,等所有数据都写到临时目录后,Minio 才会进行 rename 操作,将 object 数据存储到最终存储目录。
2.6 Meta 数据落盘
除了 Object 数据,Minio 还会针对 object 生成 meta 数据,并以 json 格式,存储到每个磁盘下(和 Object 数据同一目录)。Meta 格式如下:
// A xlMetaV1 represents xl.json
metadata header.
type xlMetaV1 struct {
Version string json:"version"
// Version of the current xl.json
.
Format string json:"format"
// Format of the current xl.json
.
Stat statInfo json:"stat"
// Stat of the current object xl.json
.
// Erasure coded info for the current object xl.json
.
Erasure ErasureInfo json:"erasure"
// MinIO release tag for current object xl.json
.
Minio struct {
Release string json:"release"
} json:"minio"
// Metadata map for current object xl.json
.
Meta map[string]string json:"meta,omitempty"
// Captures all the individual object xl.json
.
Parts []ObjectPartInfo json:"parts,omitempty"
}