【分散式技術專題】「OSS中介軟體系列」Minio的檔案服務的儲存模型及整合Java客戶端訪問的實戰指南
Minio的元資料
資料儲存
MinIO物件儲存系統沒有元資料資料庫,所有的操作都是物件級別的粒度的,這種做法的優勢是:
-
個別物件的失效,不會溢位為更大級別的系統失效。
-
便於實現“強一致性”這個特性。此特性對於機器學習與大資料處理非常重要。
資料管理
元資料與資料一起存放在磁碟上:資料部分糾刪分片以後儲存在磁碟上,元資料以明文形式存放在元資料檔案裡(xl.json)。假定物件名字為obj-with-metadata, 它所在的桶的名字是bucket_name, disk是該物件所在糾刪組的任一個磁碟的路徑,如下目錄:
disk/bucket_name/obj-with-metadata
記錄了這個物件在此磁碟上的資訊。其中的內容如下:
xl.json
xl.json即是此物件的元資料檔案。物件的元資料檔案xl.json的內容是如下這種形式的json字串:
欄位說明
format欄位
該欄位指明瞭這個物件的格式是xl,MinIO內部儲存資料主要有兩種資料格式:xl與fs。使用如下命令啟動的MinIO使用的儲存格式是fs:
這種模式主要用於測試, 物件儲存很多API都是並沒有真正實現的樁函式。在生產環境所用的部署方式(本地分散式叢集部署、聯盟模式部署、雲網關模式部署)中,儲存格式都是xl。
part.1 :物件的第一個資料分片
stat欄位
記錄了此物件的狀態,包括大小與修改時間,如下圖:
erasure欄位
這個欄位記錄此物件與糾刪碼有關的資訊,如下圖:
其中的algorithm指明瞭此物件採用的是Klaus Post實現的糾刪碼,生成矩陣是範德蒙矩陣。
-
data,parity指明瞭糾刪組中資料盤、校驗盤的個數。
-
blockSize 指明瞭物件被分塊的大小,預設是5M(請參見上一節“資料分佈與均衡”)。
-
index指明瞭當前磁碟在糾刪組中的序號。
-
distribution:每個糾刪組的資料盤、校驗盤的個數是固定的,但是不同的物件的分片寫入這個糾刪組的不同磁碟的順序是不同的。這裡記錄了分佈順序。
-
checksum:它下面的欄位個數跟此物件的分片數量有關。在舊版本的MinIO物件儲存系統,每一個分片經過hash函式計算出的checksum會記錄在元資料檔案的這個位置。最新版的MinIO會把checksum直接計入分片檔案(即part.1等檔案)的前32個位元組。
此欄位之下algorithm的值是”highwayhash256S”表明checksum值是寫入分片檔案的。
Minio的整合Java客戶端
檔案伺服器在用minio,沒有獨立成微服務也沒有抽取starter,所以簡單測試一下整合和抽取starter,建立springboot專案整合minio把檔案上傳成功
Maven環境的pom依賴
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>6.0.11</version>
</dependency>
spring的yml配置:
minio:
endpoint: http://192.168.8.50:9000
accessKey: admin
secretKey: 123123123
配置類 MinioProperties :
@Data
@ConfigurationProperties(prefix = "minio")
public class MinioProperties {
//連線url
private String endpoint;
//使用者名稱
private String accessKey;
//密碼
private String secretKey;
}
工具類 MinioUtil
import cn.hutool.core.util.StrUtil;
import com.team.common.core.constant.enums.BaseResultEnum;
import com.team.common.core.exception.BusinessException;
import io.minio.MinioClient;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
@AllArgsConstructor
@Component
public class MinioUtil {
private final MinioClient minioClient;
private final MinioProperties minioProperties;
/**
* http檔案上傳
* @param bucketName
* @param file
* @return 訪問地址
*/
public String putFile(String bucketName,MultipartFile file) {
return this.putFile(bucketName,null,file);
}
/**
* http檔案上傳(增加根路徑)
* @param bucketName
* @param folder
* @param file
* @return 訪問地址
*/
public String putFile(String bucketName,String folder,MultipartFile file) {
String originalFilename = file.getOriginalFilename();
if (StrUtil.isNotEmpty(folder)){
originalFilename = folder.concat("/").concat(originalFilename);
}
try {
InputStream in = file.getInputStream();
String contentType= file.getContentType();
minioClient.putObject(bucketName,originalFilename,in,null, null, null, contentType);
} catch (Exception e) {
e.printStackTrace();
throw new BusinessException(BaseResultEnum.SYSTEM_EXCEPTION.getCode(),"檔案上傳失敗");
}
String url = minioProperties.getEndpoint().concat("/").concat(bucketName).concat("/").concat(originalFilename);
return url;
}
/**
* 建立bucket
* @param bucketName
*/
public void createBucket(String bucketName){
try {
minioClient.makeBucket(bucketName);
} catch (Exception e) {
e.printStackTrace();
throw new BusinessException(BaseResultEnum.SYSTEM_EXCEPTION.getCode(),"建立bucket失敗");
}
}
@SneakyThrows
public String getBucketPolicy(String bucketName){
return minioClient.getBucketPolicy(bucketName);
}
}
裝配類:
import io.minio.MinioClient;
import io.minio.errors.InvalidEndpointException;
import io.minio.errors.InvalidPortException;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@AllArgsConstructor
@Configuration
@EnableConfigurationProperties(MinioProperties.class)
public class MinioAutoConfiguration {
private final MinioProperties minioProperties;
@Bean
public MinioClient minioClient() throws InvalidPortException, InvalidEndpointException {
MinioClient client = new MinioClient(minioProperties.getEndpoint(),minioProperties.getAccessKey(),minioProperties.getSecretKey());
return client;
}
@ConditionalOnBean(MinioClient.class)
@Bean
public MinioUtil minioUtil(MinioClient minioClient,MinioProperties minioProperties) {
return new MinioUtil(minioClient,minioProperties);
}
}
spring.factories配置檔案
去掉主入口函式,去掉application.properties配置檔案(新建一個測試用的springboot專案,把配置檔案拿過去)
剩下最重要的一步:在resources下建立META-INF/spring.factories檔案,配置檔案中加入需要自動裝配的類
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.*(你的路徑).MinioAutoConfiguration
demo:
import com.team.common.core.web.Result;
import com.team.common.minio.MinioUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@Api(value = "uploadFile", tags = "檔案上傳")
@RequestMapping("uploadFile")
@RestController
public class UploadFileController {
@Autowired
private MinioUtil minioUtil;
@ApiOperation(value = "通用檔案上傳")
@PutMapping("/upload")
public Result uploadFile(@ApiParam("儲存桶名稱") String bucketName,@ApiParam("檔案") MultipartFile file) {
String url = null;
try {
url = minioUtil.putFile(bucketName,file);
} catch (Exception e) {
e.printStackTrace();
}
return Result.success(url);
}
}
打包安裝到maven倉庫,本地測試用的同一倉庫地址的話可以直接maven install,新建一個springboot專案,填入application.properties,pom中增加starter的依賴。
<dependency>
<groupId>com.jxwy</groupId>
<artifactId>minio-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
其他OSS服務對比
廠商支援
國內使用Ceph的廠商、基於Ceph進行自研的儲存廠商都比較多,在使用過程中遇到的問題(有些時候,甚至需要修改、增強乃至重新實現Ceph本身的功能),可以向相關廠商尋求支援。國際方面,Ceph早已被紅帽收購,而紅帽近期又被IBM收購。
MinIO開發與支援的廠商只有MinIO公司。由於架構比較先進,語言高階,MinIO本身的程式比較容易讀懂、修改。招聘Golang程式設計師來 維護MinIO所花費的成本,顯然低於招聘c++程式設計師來維護Ceph。
多語言客戶端SDK
二者均有常見程式語言的客戶端,比如:python, java等。MinIO物件儲存軟體的開發SDK另外支援純函式式的語言Haskell。
技術文件
內部實現的文件MinIO基本不存在。想要了解內部實現乃至參與開發的技術人員,只能到如下社群:http://minio.slack.com/ ,與MinIO的開發人員直接交流,或者自己閱讀程式碼。Ceph的各種實現文件、演算法說明文件非常豐富。這方面Ceph要比MinIO成熟很多。
Ceph和MinIO的對比
開源物件儲存軟體以MinIO,Ceph為典型代表。為幫助相關人員在選擇物件儲存系統之時選擇合適的產品,此處對二者的特點、特性做一定討論。
MinIO優勢
部署極其簡單
MinIO系統的服務程式僅有minio一個可執行檔案,基本不依賴其它共享庫或者rpm/apt包。minio的配置項很少(大部分都是核心之類系統級的設定),甚至不配置也可以正常執行起來。百度、google、bing等搜尋引擎上基本沒有關於MinIO部署問題的網頁,可見在實踐中,很少有使用者遇到這方面的問題。
相比之下,Ceph系統的模組,相關的rpm、apt包眾多,配置項非常多,難以部署,難調優。某些Linux發行版的Ceph安裝包甚至有bug,需要使用者手動改動Ceph的python指令碼,才能安裝完畢。
二次開發容易
MinIO物件儲存系統除了極少數程式碼使用匯編實現以外,全部使用Golang語言實現。Ceph系統是使用業界聞名的難學難用的c++語言編寫的。Golang語言由於產生較晚,吸收了很多語言尤其是c++的教訓,語言特性比較現代化。
相對而言,MinIO系統的維護、二次開發比較容易。
網管模式支援多種其他儲存
通過閘道器模式,MinIO物件儲存後端,可以對接各種現有的常見其它儲存型別,比如的NAS系統,微軟Azure Blob 儲存、Google 雲端儲存、HDFS、阿里巴巴OSS、亞馬遜S3等,非常有利於企業複用現有資源,有利於企業低成本(硬體成本約等於零,部署MinIO物件儲存軟體即可)地從現有系統平滑升級到物件儲存。
Ceph優勢
資料冗餘策略更加豐富,Ceph同時支援副本、糾刪碼,而MinIO只支援糾刪碼。對於個別的對於資料可靠性要求極高的單位,Ceph物件儲存更加合適。
參考硬體
MinIO是符合軟體定義儲存SDS理念的,相容主流X86伺服器以及ARM/飛騰平臺,同時也可以移植到諸如申威(Alpha架構)和龍芯(Mips架構)等硬體平臺。
下面這些符合工業標準的、廣泛採用的伺服器是經過MinIO inc.優化測試過的、MinIO物件儲存軟體表現優異的伺服器:
結論
由以上討論,可見作為物件儲存軟體來說,MinIO, Ceph都非常優秀,各自有各自的優勢。準備使用物件儲存軟體的使用者,應該根據自己單位的需求、技術儲備等實際情況,選擇適當的軟體。
參考資料
-
https://github.com/krishnasrinivas/wikinotes/wiki/minio-scaling
-
https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/dev/Welcome.html
-
Klaus Post官網:https://klauspost.com/
-
https://min.io/resources/docs/MinIO-throughput-benchmarks-on-NVMe-SSD.pdf
-
https://github.com/minio/minio/blob/master/cmd/admin-heal-ops.go
-
https://github.com/klauspost/reedsolomon/blob/master/options.go
-
https://min.io/resources/docs/CPG-MinIO-implementation-guide.pdf
-
https://docs.min.io/docs/minio-bucket-notification-guide.html