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"
}