【Java互联网技术】MinIO分布式文件存储服务
应用场景
互联网海量非结构化数据的存储
基本概念
Object:存储的基本对象,如文件、字节流等
Bucket:存储Object的逻辑空间,相当于顶层文件夹
Drive:存储数据的磁盘,在MinIO启动时,以参数的方式传入
Set:多个Drive的集合。分布式部署时划分一个或多个Set,一个Object存在Set上,Set中的每个Drive中都会有相同的Object
特点
- 部署简单,整体只有一个二进制文件,还可支持各种平台
- 支持海量存储,单个对象最大可达5TB
- 兼容Amazon S3接口,充分考虑开发人员的需求和体验
- 低冗余且磁盘损坏高容忍
- 读写性能优异(HDD在16个结点的MinIO集群的读速度可达10GB/s,写速度可达8.5GB/s)
纠删码
Erasure Code(EC)。MinIO使用纠删码来保证高可靠性,使用highwayhash来处理数据损坏。n份原始数据保存后会生成m份纠删码算法转换后的数据,当有任意小于等于m份的数据损坏,就能通过剩下的额数据还原出来。
存储形式
文件上传到MinIO后,存储在对应的磁盘中,以Bucket名称为目录,文件名为下一级目录,这个目录下保存文件的编码数据块、校验块、元数据文件(json格式)
Docker部署MinIO(普通模式)
1、拉取镜像
docker pull minio/minio
2、创建挂载目录
mkdir /wanfeng/minio/data
mkdir /wanfeng/minio/config
3、运行容器
docker run -p 9000:9000 -p 9001:9001 --name wanfeng_minio --privileged=true
-e "MINIO_ROOT_USER=登录用户名"
-e "MINIO_ROOT_PASSWORD=登录密码"
-v /wanfeng/minio/data:/data
-v /wanfeng/minio/config:/root/.minio
-d minio/minio server /data --console-address ":9001"
- -p 9000:9000 -p 9001:9001:必须暴露出两个端口用户客户端和服务端
- MINIO_ROOT_USER:用户名
- MINIO_ROOT_PASSWORD:密码
- /wanfeng/minio/data:/data:数据文件映射目录
- /wanfeng/minio/config:/root/.minio:配置文件映射目录
- –console-address “:9001”:指定服务端端口号(通过该端口访问minio服务)
Docker部署MinIO(纠删码模式)
纠删码的作用就是在上传一份数据后,会生成n个原始数据块和n个奇偶校验块,若任意丢失不超过n块盘,都可以进行数据恢复。
1、在docker启动时增加多块盘
docker run -p 9000:9000 -p 9001:9001 --name wanfeng_minio --privileged=true
-e "MINIO_ROOT_USER=登录用户名"
-e "MINIO_ROOT_PASSWORD=密码"
-v /wanfeng/minio/config:/root/.minio
-v /wanfeng/minio/data1:/data1
-v /wanfeng/minio/data2:/data2
-v /wanfeng/minio/data3:/data3
-v /wanfeng/minio/data4:/data4
-v /wanfeng/minio/data5:/data5
-v /wanfeng/minio/data6:/data6
-d minio/minio server /data{1...6} --console-address ":9001"
Docker部署MinIO(分布式集群)
分布式集群可以避免单台服务器故障带来的影响(单点故障),将minio
Java API整合MinIO
1、引入依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/me.tongfei/progressbar -->
<dependency>
<groupId>me.tongfei</groupId>
<artifactId>progressbar</artifactId>
<version>0.5.3</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.10.0</version>
</dependency>
2、API使用
@SpringBootTest
public class MinioApplicationTests {
private MinioClient minioClient;
public static final String IMAGE_FILE_NAME = "wallhaven01.jpg";
public static final String TEXT_FILE_NAME = "笔记.txt";
public static final String UPLOAD_FOLDER_PATH = "src/test/resources/upload";
public static final String DOWNLOAD_FOLDER_PATH = "src/test/resources/download";
@BeforeEach
public void connectMinio(){
minioClient = MinioClient.builder().endpoint(ServerConstant.CENTOS_IP_ADDRESS, 9000, false)
.credentials(LoginConstant.MINIO_ROOT_USERNAME, LoginConstant.MINIO_ROOT_PASSWORD)
.build();
}
/**
* 本地文件上传
*/
@Test
public void uploadFile(){
try {
//判断bucket是否存在
boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket("ectest").build());
if(!bucketExists){
minioClient.makeBucket(MakeBucketArgs.builder().bucket("ectest").build());
}else{
System.out.println("Bucket[ectest] already exists");
}
/**
* bucket:指定上传的bucket
* object:上传到minio的路径(到文件名)
* filename:上传的本地文件路径(到文件名)
*/
minioClient.uploadObject(UploadObjectArgs.builder()
.bucket("ectest")
.object(IMAGE_FILE_NAME)
.filename(UPLOAD_FOLDER_PATH + File.separator + IMAGE_FILE_NAME).build());
} catch (Exception e){
e.printStackTrace();
}
}
/**
* 上传流
*/
@Test
public void uploadInputStream() throws Exception {
File file = new File(UPLOAD_FOLDER_PATH + File.separator + IMAGE_FILE_NAME);
FileInputStream fileInputStream = new FileInputStream(file);
minioClient.putObject(PutObjectArgs.builder()
.bucket("ectest")
.object("image_" + NumberFactory.getRandomNumber(5) + ".jpg" )
.stream(fileInputStream, fileInputStream.available(), -1)
.build());
fileInputStream.close();
}
/**
* 下载文件
*/
@Test
public void downloadFile(){
try {
//判断bucket是否存在
boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket("ectest").build());
if(!bucketExists){
minioClient.makeBucket(MakeBucketArgs.builder().bucket("ectest").build());
}else{
System.out.println("Bucket[ectest] already exists");
}
/**
* bucket:指定上传的bucket
* object:minio中的文件路径
* filename:下载到本地的路径(到文件名)
*/
minioClient.downloadObject(DownloadObjectArgs.builder()
.bucket("ectest")
.object(IMAGE_FILE_NAME)
.filename(DOWNLOAD_FOLDER_PATH + File.separator + "minio_image_" + NumberFactory.getRandomNumber(8) + ".jpg").build());
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 下载流
*/
@Test
public void downloadInputStream() throws Exception{
//获取流
InputStream inputStream = minioClient.getObject(GetObjectArgs.builder()
.bucket("ectest")
.object(IMAGE_FILE_NAME)
.build());
File file = new File(DOWNLOAD_FOLDER_PATH + File.separator + "image_aaa.jpg");
FileUtils.copyInputStreamToFile(inputStream, file);
inputStream.close();
//直接把流保存到本地文件
minioClient.downloadObject(DownloadObjectArgs.builder()
.bucket("ectest")
.object(IMAGE_FILE_NAME)
.filename(DOWNLOAD_FOLDER_PATH + File.separator + "image_bbb.jpg").build());
}
}
SpringBoot整合MinIO
本质就是把连接minio服务的参数放在配置文件中,并通过Bean对象在SpringBoor应用启动时生成MinioClient对象。
1、配置文件application.yml
minio:
endpoint: http://ip:port
rootUserName: 用户名
rootPassword: 密码
bucketName: bucket名称
2、读取配置属性的类
@Data
@Component
@ConfigurationProperties(prefix = "minio")
public class MinioProperties {
private String endpoint;
private String rootUserName;
private String rootPassword;
private String bucketName;
}
3、生成MinioClient对象的类
@Configuration
public class MinioClientFactory {
@Resource
private MinioProperties minioProperties;
@Bean
public MinioClient getDefaultMinioClient(){
return MinioClient.builder()
.endpoint(minioProperties.getEndpoint())
.credentials(minioProperties.getRootUserName(), minioProperties.getRootPassword())
.build();
}
}
4、编写controller测试
@RestController
@Slf4j
@RequestMapping("/minio")
public class MinioController {
@Resource
private MinioClient minioClient;
@Value("${minio.bucketName}")
private String bucketName;
@GetMapping("/list_object_name")
public List<String> listObject() throws Exception {
Iterable<Result<Item>> objectResults = minioClient.listObjects(ListObjectsArgs.builder()
.bucket(bucketName).build());
List<String> objectNameList = new ArrayList<>();
for (Result<Item> objectResult : objectResults) {
Item item = objectResult.get();
objectNameList.add(item.objectName());
}
return objectNameList;
}
}