Minio 文件服務(wù)(1)—— Minio部署使用及存儲機(jī)制分析
Minio 文件服務(wù)(2)—— Minio用Nginx做負(fù)載均衡
本文參考Minio官方文檔,使用細(xì)節(jié)里面說的很詳細(xì),本文主要講解文檔中較少涉及的Minio存儲機(jī)制。以及我在使用中部署和使用Java SDK訪問的過程。
簡介
Minio 是一個基于Apache License v2.0開源協(xié)議的
對象存儲服務(wù)。它兼容亞馬遜S3云存儲服務(wù)接口,非常適合于存儲大容量非結(jié)構(gòu)化的數(shù)據(jù),例如圖片、視頻、日志文件、備份數(shù)據(jù)和容器/虛擬機(jī)鏡像等,而一個對象文件可以是任意大小,從幾kb到最大5T不等。
Minio是一個非常輕量的服務(wù),可以很簡單的和其他應(yīng)用的結(jié)合,類似 NodeJS, Redis 或者 MySQL。
存儲機(jī)制
Minio使用糾刪碼
erasure code和校驗和checksum來保護(hù)數(shù)據(jù)免受硬件故障和無聲數(shù)據(jù)損壞。 即便丟失一半數(shù)量(N/2)的硬盤,仍然可以恢復(fù)數(shù)據(jù)。
糾刪碼
糾刪碼是一種恢復(fù)丟失和損壞數(shù)據(jù)的數(shù)學(xué)算法,目前,糾刪碼技術(shù)在分布式存儲系統(tǒng)中的應(yīng)用主要有三類,陣列糾刪碼(Array Code: RAID5、RAID6等)、RS(Reed-Solomon)里德-所羅門類糾刪碼和LDPC(LowDensity Parity Check Code)低密度奇偶校驗糾刪碼。Erasure Code是一種編碼技術(shù),它可以將n份原始數(shù)據(jù),增加m份數(shù)據(jù),并能通過n+m份中的任意n份數(shù)據(jù),還原為原始數(shù)據(jù)。即如果有任意小于等于m份的數(shù)據(jù)失效,仍然能通過剩下的數(shù)據(jù)還原出來。
Minio采用Reed-Solomon code將對象拆分成N/2數(shù)據(jù)和N/2 奇偶校驗塊。因此下面主要講解RS類糾刪碼。
RS code編碼數(shù)據(jù)恢復(fù)原理:
RS編碼以word為編碼和解碼單位,大的數(shù)據(jù)塊拆分到字長為w(取值一般為8或者16位)的word,然后對word進(jìn)行編解碼。 數(shù)據(jù)塊的編碼原理與word編碼原理相同,后文中以word為例說明,變量Di, Ci將代表一個word。
把輸入數(shù)據(jù)視為向量D=(D1,D2,..., Dn), 編碼后數(shù)據(jù)視為向量(D1, D2,..., Dn, C1, C2,.., Cm),RS編碼可視為如下(圖1)所示矩陣運(yùn)算。
圖1最左邊是編碼矩陣(或稱為生成矩陣、分布矩陣,Distribution Matrix),編碼矩陣需要滿足任意n*n子矩陣可逆。為方便數(shù)據(jù)存儲,編碼矩陣上部是單位陣(n行n列),下部是m行n列矩陣。下部矩陣可以選擇范德蒙德矩陣或柯西矩陣。

RS最多能容忍m個數(shù)據(jù)塊被刪除。 數(shù)據(jù)恢復(fù)的過程如下:
(1)假設(shè)D1、D4、C2丟失,從編碼矩陣中刪掉丟失的數(shù)據(jù)塊/編碼塊對應(yīng)的行。(圖2、3)
(2)由于B' 是可逆的,記B'的逆矩陣為 (B'^-1),則B' * (B'^-1) = I 單位矩陣。兩邊左乘B' 逆矩陣。 (圖4、5)
(3)得到如下原始數(shù)據(jù)D的計算公式 。

(4)對D重新編碼,可得到丟失的編碼碼
實(shí)踐
Minio采用Reed-Solomon code將對象拆分成N/2數(shù)據(jù)和N/2 奇偶校驗塊。 這就意味著如果是12塊盤,一個對象會被分成6個數(shù)據(jù)塊、6個奇偶校驗塊,可以丟失任意6塊盤(不管其是存放的數(shù)據(jù)塊還是奇偶校驗塊),仍可以從剩下的盤中的數(shù)據(jù)進(jìn)行恢復(fù)。
以下是我在4個節(jié)點(diǎn)上部署的集群,部署代碼如下面多節(jié)點(diǎn)部署所示。
1、我在minio服務(wù)中上傳了一個hello.txt文件,內(nèi)容為:hello,how are you?\n 共20個字符。
2、然后在對應(yīng)的bucket里會生成一個名為hello.txt的文件夾,文件夾里有2個文件,查看4個節(jié)點(diǎn)的part.1文件,會發(fā)現(xiàn)其中2個文件均分存放了原始數(shù)據(jù)hello,how are you?\n ,另外2個文件是亂碼,但是根據(jù)上述分析應(yīng)該存的就是奇偶校驗塊,充當(dāng)編碼矩陣的作用。因此part.1文件是存儲就刪碼的。
(數(shù)據(jù)塊和奇偶校驗塊所存儲的節(jié)點(diǎn)位置不是固定不變的)

3、而另一個json文件中的內(nèi)容如下,對比了4個節(jié)點(diǎn)xl.json文件的差別,發(fā)現(xiàn)只有index和checksum中的hash值不同。因此這個json文件是存儲校驗和的。保護(hù)數(shù)據(jù)免受無聲數(shù)據(jù)損壞。

模擬數(shù)據(jù)丟失的情況

新建一個happy.txt文件,上傳至文件服務(wù),在服務(wù)器的4個節(jié)點(diǎn)上可以看見該文件生成了一個part.1和xl.json文件。
第一步刪除005號服務(wù)器上的數(shù)據(jù)塊,下載該文件,可讀;
第二步刪除004號服務(wù)器上的數(shù)據(jù)塊,下載該文件,可讀;
第二步刪除003號服務(wù)器上的數(shù)據(jù)塊,下載該文件,不可讀,得到空白文件;因為丟失的硬盤數(shù)量大于N/2,不可恢復(fù)健康數(shù)據(jù)。
模擬checksum丟失的情況

新建一個sing.txt文件,上傳至文件服務(wù),在服務(wù)器的4個節(jié)點(diǎn)上可以看見該文件生成了一個part.1和xl.json文件。
第一步刪除005號服務(wù)器上的校驗塊,下載該文件,可讀;
第二步刪除004號服務(wù)器上的校驗塊,下載該文件,可讀;
第二步刪除003號服務(wù)器上的校驗塊,下載該文件,不可讀,頁面報異常,返回頁面后該文件已不存在。
再看服務(wù)器中但存儲,文件名變?yōu)?CORRUPTED后綴。文件被損壞。

單機(jī)Minio服務(wù)存在單點(diǎn)故障,相反,如果是一個N節(jié)點(diǎn)的分布式Minio,只要有N/2節(jié)點(diǎn)在線,你的數(shù)據(jù)就是安全的。不過你需要至少有N/2+1個節(jié)點(diǎn) Quorum 來創(chuàng)建新的對象。
例如,一個8節(jié)點(diǎn)的Minio集群,每個節(jié)點(diǎn)一塊盤,就算4個節(jié)點(diǎn)宕機(jī),這個集群仍然是可讀的,不過你需要5個節(jié)點(diǎn)才能寫數(shù)據(jù)。
部署
單節(jié)點(diǎn)
(容器部署)
docker pull minio/minio
#在Docker中運(yùn)行Minio單點(diǎn)模式
docker run -p 9000:9000 -e MINIO_ACCESS_KEY=sunseaiot -e MINIO_SECRET_KEY=sunseaiot minio/minio server /data
#要創(chuàng)建具有永久存儲的Minio容器,您需要將本地持久目錄從主機(jī)操作系統(tǒng)映射到虛擬配置~/.minio 并導(dǎo)出/data目錄
#建立外掛文件夾 /Users/hbl/dockersp/volume/minio/
docker run -p 9000:9000 -e MINIO_ACCESS_KEY=sunseaiot -e MINIO_SECRET_KEY=sunseaiot -v /Users/hbl/dockersp/volume/minio/data:/data -v /Users/hbl/dockersp/volume/minio/config:/root/.minio minio/minio server /data
多節(jié)點(diǎn)
分布式搭建的流程和單節(jié)點(diǎn)基本一樣,Minio服務(wù)基于命令行傳入的參數(shù)自動切換成單機(jī)模式還是分布式模式。
分布式Minio單租戶存在最少4個盤最多16個盤的限制(受限于糾刪碼)。這種限制確保了Minio的簡潔,同時仍擁有伸縮性。如果你需要搭建一個多租戶環(huán)境,你可以輕松的使用編排工具(Kubernetes)來管理多個Minio實(shí)例。
糾刪碼 (多塊硬盤 / 服務(wù))
| 項目 | 參數(shù) |
|---|---|
| 最大驅(qū)動器數(shù)量 | 16 |
| 最小驅(qū)動器數(shù)量 | 4 |
| 讀仲裁 | N / 2 |
| 寫仲裁 | N / 2+1 |
(多節(jié)點(diǎn)部署如果要使用容器需要用docker swarm 和K8s,文檔中有介紹,本文重點(diǎn)不在此因此我直接在4個服務(wù)器上安裝了Minio,直接運(yùn)行即可。Minio服務(wù)基于命令行傳入的參數(shù)自動切換成單機(jī)模式還是分布式模式,啟動一個分布式Minio實(shí)例,你只需要把硬盤位置做為參數(shù)傳給minio server命令即可,然后,你需要在所有其它節(jié)點(diǎn)運(yùn)行同樣的命令。)
部署4主機(jī),每機(jī)單塊磁盤(drive)
export MINIO_ACCESS_KEY=123456
export MINIO_SECRET_KEY=123456
./minio server http://192.168.8.110/export1 \
http://192.168.8.111/export2 \
http://192.168.8.112/export3 \
http://192.168.8.113/export4
部署4主機(jī),每機(jī)2塊磁盤(drives)
export MINIO_ACCESS_KEY=123456
export MINIO_SECRET_KEY=123456
./minio server http://192.168.8.110/export1 http://192.168.1.110/export2 \
http://192.168.8.111/export1 http://192.168.1.111/export2 \
http://192.168.8.112/export1 http://192.168.1.112/export2 \
http://192.168.8.113/export1 http://192.168.1.113/export2
后臺運(yùn)行
由于不是用docker部署的,所以需要將進(jìn)程加入后臺運(yùn)行。使用nohup指令。
export MINIO_ACCESS_KEY=SunseaIoT2018!
export MINIO_SECRET_KEY=SunseaIoT2018!
nohup ./minio server http://192.168.8.110/minio1 \
http://192.168.8.111/minio2 \
http://192.168.8.112/minio3 \
http://192.168.8.113/minio4 > out.file 2>&1 &
使用
部署好Minio服務(wù)后可以通過瀏覽器訪問。輸入設(shè)置好的用戶名和密碼即可進(jìn)行操作。

Java SDK訪問Minio服務(wù)
package com.minio.client;
import io.minio.MinioClient;
import io.minio.errors.MinioException;
import lombok.extern.slf4j.Slf4j;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
@Slf4j
public class FileUploader {
public static void main(String[] args) throws NoSuchAlgorithmException, IOException, InvalidKeyException, XmlPullParserException {
try {
MinioClient minioClient = new MinioClient("https://minio.sunseaiot.com", "sunseaiot", "sunseaiot",true);
// 檢查存儲桶是否已經(jīng)存在
if(minioClient.bucketExists("ota")) {
log.info("Bucket already exists.");
} else {
// 創(chuàng)建一個名為ota的存儲桶
minioClient.makeBucket("ota");
log.info("create a new bucket.");
}
//獲取下載文件的url,直接點(diǎn)擊該url即可在瀏覽器中下載文件
String url = minioClient.presignedGetObject("ota","hello.txt");
log.info(url);
//獲取上傳文件的url,這個url可以用Postman工具測試,在body里放入需要上傳的文件即可
String url2 = minioClient.presignedPutObject("ota","ubuntu.tar");
log.info(url2);
// 下載文件到本地
minioClient.getObject("ota","hello.txt", "/Users/hbl/hello2.txt");
log.info("get");
// 使用putObject上傳一個本地文件到存儲桶中。
minioClient.putObject("ota","tenant2/hello.txt", "/Users/hbl/hello.txt");
log.info("/Users/hbl/hello.txt is successfully uploaded as hello.txt to `task1` bucket.");
} catch(MinioException e) {
log.error("Error occurred: " + e);
}
}
}