1. 程式人生 > 其它 >【分散式技術專題】「OSS中介軟體系列」Minio的檔案服務的儲存模型及整合Java客戶端訪問的實戰指南

【分散式技術專題】「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都非常優秀,各自有各自的優勢。準備使用物件儲存軟體的使用者,應該根據自己單位的需求、技術儲備等實際情況,選擇適當的軟體。

參考資料

極限就是為了超越而存在的