介绍
1、分布式文件系统
简单理解为:一个计算机无法存储海量的文件,通过网络将若干计算机组织起来共同去存储海量的文件,去接收海量用户的请求,这些组织起来的计算机通过网络进行通信。
好处:
- 一台计算机的文件系统处理能力扩充到多台计算机同时处理。
- 一台计算机挂了还有另外副本计算机提供数据。
- 每台计算机可以放在不同的地域,这样用户就可以就近访问,提高访问速度。
2、市面上有哪些分布式文件系统的产品呢?
2.1、网络文件系统(NFS)
特点:
- 在客户端上映射NFS服务器的驱动器。
- 客户端通过网络访问NFS服务器的硬盘完全透明。
2.2、GFS
特点:
- GFS采用主从结构,一个GFS集群由一个master和大量的chunkserver组成。
- master存储了数据文件的元数据,一个文件被分成了若干块存储在多个chunkserver中。
- 用户从master中获取数据元信息,向chunkserver存储数据。
2.3、HDFS
HDFS,是Hadoop Distributed File System的简称,是Hadoop抽象文件系统的一种实现。HDFS是一个高度容错性的系统,适合部署在廉价的机器上。HDFS能提供高吞吐量的数据访问,非常适合大规模数据集上的应用。 HDFS的文件分布在集群机器上,同时提供副本进行容错及可靠性保证。例如客户端写入读取文件的直接操作都是分布在集群各个机器上的,没有单点性能压力。
特点:
- HDFS采用主从结构,一个HDFS集群由一个名称结点和若干数据结点组成。
- 名称结点存储数据的元信息,一个完整的数据文件分成若干块存储在数据结点。
- 客户端从名称结点获取数据的元信息及数据分块的信息,得到信息客户端即可从数据块来存取数据。
2.4、云计算厂家
- 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。其数据设计持久性不低于 99.9999999999%(12 个 9),服务设计可用性(或业务连续性)不低于 99.995%。
官方网站:https://www.aliyun.com/product/oss - 百度对象存储BOS提供稳定、安全、高效、高可扩展的云存储服务。您可以将任意数量和形式的非结构化数据存入BOS,并对数据进行管理和处理。BOS支持标准、低频、冷和归档存储等多种存储类型,满足多场景的存储需求。
官方网站:https://cloud.baidu.com/product/bos.html
3、MinIO
3.1、介绍
官网:https://min.io
中文:https://www.minio.org.cn/,http://docs.minio.org.cn/docs/
本项目采用MinIO构建分布式文件系统,MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合使用,它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等。
它一大特点就是轻量,使用简单,功能强大,支持各种平台,单个文件最大5TB,兼容 Amazon S3接口,提供了 Java、Python、GO等多版本SDK支持。
MinIO集群采用去中心化共享架构,每个结点是对等关系,通过Nginx可对MinIO进行负载均衡访问。
去中心化有什么好处?
- 在大数据领域,通常的设计理念都是无中心和分布式。Minio分布式模式可以帮助你搭建一个高可用的对象存储服务,你可以使用这些存储设备,而不用考虑其真实物理位置。
- 它将分布在不同服务器上的多块硬盘组成一个对象存储服务。由于硬盘分布在不同的节点上,分布式Minio避免了单点故障。如下图:
Minio使用纠删码技术来保护数据,它是一种恢复丢失和损坏数据的数学算法,它将数据分块冗余的分散存储在各各节点的磁盘上,所有的可用磁盘组成一个集合,上图由8块硬盘组成一个集合,当上传一个文件时会通过纠删码算法计算对文件进行分块存储,除了将文件本身分成4个数据块,还会生成4个校验块,数据块和校验块会分散的存储在这8块硬盘上。
使用纠删码的好处是即便丢失一半数量(N/2)的硬盘,仍然可以恢复数据。 比如上边集合中有4个以内的硬盘损害仍可保证数据恢复,不影响上传和下载,如果多于一半的硬盘坏了则无法恢复。
3.2、Windows安装测试
3.2.1、下载安装
在官网下载minio.exe文件即可
地址:https://www.minio.org.cn/download.shtml#/windows
3.2.2、启动
CMD进入有minio.exe的目录,运行下边的命令:
minio.exe server D:\my\minio_data\data1 D:\my\minio_data\data2 D:\my\minio_data\data3 D:\my\minio_data\data4
- 老版本使用的MINIO_ACCESS_KEY 和 MINIO_SECRET_KEY不推荐使用,推荐使用MINIO_ROOT_USER 和MINIO_ROOT_PASSWORD设置账号和密码。
- pool即minio节点组成的池子,当前有一个pool和4个硬盘组成的set集合
- 因为集合是4个硬盘,大于2的硬盘损坏数据将无法恢复。
- 账号和密码默认为minioadmin、minioadmin,可以在环境变量中设置通过’MINIO_ROOT_USER’ and ‘MINIO_ROOT_PASSWORD’ 进行设置。
3.2.3、访问,登录
http://localhost:9000
默认账号密码:minioadmin
3.3.4、创建Bucket桶
- 系统页面创建
- 存储目录查看
3.2.5、上传文件
- 系统页面上传
- 目录查看
SDK操作(java代码)
1、引入依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.4.3</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.8.1</version>
</dependency>
<!-- 测试的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
2、上传文件
连接
- endpoint:minio服务的地址
- credentials:用户密码
上传
- bucket:桶的名称
- object:上传后的对象名称
- filename:要上传的文件的路径
public class MinIOTest {
// 连接minio
static MinioClient minioClient =
MinioClient.builder()
.endpoint("http://localhost:9000")
.credentials("minioadmin", "minioadmin")
.build();
@Test
public void upload() {
try {
UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
.bucket("myfile")
.object("刘亦菲.jpeg")//同一个桶内对象名不能重复
.filename("C:\\Users\\86152\\Pictures\\Camera Roll\\lyf01.jpeg")
.build();
//上传
minioClient.uploadObject(uploadObjectArgs);
System.out.println("上传成功了");
} catch (Exception e) {
System.out.println("上传失败");
}
}
}
3、删除文件
//删除文件
@Test
public void delete() {
try {
RemoveObjectArgs removeObjectArgs = RemoveObjectArgs
.builder()
.bucket("myfile")
.object("刘亦菲.jpeg")
.build();
minioClient.removeObject(removeObjectArgs);
} catch (Exception e) {
}
}
没有了
4、查询文件(下载文件)
//查询文件
@Test
public void getFile() {
GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket("myfile").object("高圆圆.jpeg").build();
try(
FilterInputStream inputStream = minioClient.getObject(getObjectArgs);
FileOutputStream outputStream = new FileOutputStream(new File("D:\\my\\minio_data\\gyy.jpeg"));
) {
if(inputStream!=null){
IOUtils.copy(inputStream,outputStream);
}
} catch (Exception e) {
}
}
文件上传(实战)
1、引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.4.3</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.8.1</version>
</dependency>
<!-- 测试的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!-- mybatis plus 代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.9</version>
</dependency>
<!-- druid 连接池管理 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<!-- mySQL数据库驱动包管理 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- fastjson ,json解析工具 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<!-- 工具类管理 -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<!--google推荐的一套工具类库-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>25.0-jre</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
<!-- Servlet 容器管理 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- Spring Boot 集成 log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- 排除 Spring Boot 依赖的日志包冲突 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
2、环境配置
server.port=9001
spring.application.name=file
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url: jdbc:mysql://localhost:3306/hm?serverTimezone=UTC&userUnicode=true&useSSL=false&
spring.datasource.username: root
spring.datasource.password: zzybzb
# 日志文件配置路径
logging.config=classpath:log4j2-dev.xml
minio.endpoint=http://localhost:9000
minio.accessKey=minioadmin
minio.secretKey=minioadmin
minio.bucket.files=myfile
spring.servlet.multipart.max-file-size=50MB
spring.servlet.multipart.max-request-size=50MB
3、关键代码
3.1、minio配置类
package com.file.config;
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
minio配置类
*/
@Configuration
public class MinioConfig {
//读取参数
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.accessKey}")
private String accessKey;
@Value("${minio.secretKey}")
private String secretKey;
@Bean
public MinioClient minioClient() {
MinioClient minioClient =
MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
return minioClient;
}
}
3.2、controller层代码
@RequestMapping(value = "/upload/coursefile", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public UploadFileResultDto upload(@RequestPart("filedata") MultipartFile filedata,
@RequestParam(value = "folder",required=false) String folder,
@RequestParam(value= "objectName",required=false) String objectName) {
Long companyId = 1232141425L;
UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();
String contentType = filedata.getContentType();
uploadFileParamsDto.setContentType(contentType);
uploadFileParamsDto.setFileSize(filedata.getSize());//文件大小
if (contentType.indexOf("image") >= 0) {
//是个图片
uploadFileParamsDto.setFileType("001001");
} else {
uploadFileParamsDto.setFileType("001003");
}
uploadFileParamsDto.setFilename(filedata.getOriginalFilename());//文件名称
UploadFileResultDto uploadFileResultDto = null;
try {
uploadFileResultDto = mediaFileService.uploadFile(companyId, uploadFileParamsDto, filedata.getBytes(), folder, objectName);
} catch (Exception e) {
XueChengPlusException.cast("上传文件过程中出错");
}
return uploadFileResultDto;
}
3.3、service层代码
- service
public interface MediaFileService {
/**
* @description 上传文件的通用接口
* @param companyId 机构id
* @param uploadFileParamsDto 文件信息
* @param bytes 文件字节数组
* @param folder 桶下边的子目录
* @param objectName 对象名称
*/
public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, byte[] bytes, String folder, String objectName);
}
- impl代码
@Slf4j
@Service
public class MediaFileServiceImpl implements MediaFileService {
@Autowired
MediaFilesMapper mediaFilesMapper;
@Autowired
MinioClient minioClient;
@Value("${minio.bucket.files}")
private String bucket_files;
@Override
public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, byte[] bytes, String folder, String objectName) {
//得到文件的md5值
String fileMd5 = DigestUtils.md5DigestAsHex(bytes);
if(StringUtils.isEmpty(folder)){
//自动生成目录的路径 按年月日生成,
folder = getFileFolder(new Date(), true, true, true);
}else if(folder.indexOf("/")<0){
folder = folder+"/";
}
//文件名称
String filename = uploadFileParamsDto.getFilename();
if(StringUtils.isEmpty(objectName)){
//如果objectName为空,使用文件的md5值为objectName
objectName = fileMd5 + filename.substring(filename.lastIndexOf("."));
}
objectName = folder + objectName;
try {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
String contentType = uploadFileParamsDto.getContentType();
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(bucket_files)
.object(objectName)
//InputStream stream, long objectSize 对象大小, long partSize 分片大小(-1表示5M,最大不要超过5T,最多10000)
.stream(byteArrayInputStream, byteArrayInputStream.available(), -1)
.contentType(contentType)
.build();
//上传到minio
minioClient.putObject(putObjectArgs);
//保存到数据库
MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
if(mediaFiles == null){
mediaFiles = new MediaFiles();
//封装数据
BeanUtils.copyProperties(uploadFileParamsDto,mediaFiles);
mediaFiles.setId(fileMd5);
mediaFiles.setFileId(fileMd5);
mediaFiles.setCompanyId(companyId);
mediaFiles.setFilename(filename);
mediaFiles.setBucket(bucket_files);
mediaFiles.setFilePath(objectName);
mediaFiles.setUrl("/"+bucket_files+"/"+objectName);
mediaFiles.setCreateDate(new Date());
mediaFiles.setStatus("1");
mediaFiles.setAuditStatus("002003");
//插入文件表
mediaFilesMapper.insert(mediaFiles);
}
//准备返回数据
UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
BeanUtils.copyProperties(mediaFiles,uploadFileResultDto);
return uploadFileResultDto;
} catch (Exception e) {
log.debug("上传文件失败:{}",e.getMessage());
}
return null;
}
//根据日期拼接目录
private String getFileFolder(Date date, boolean year, boolean month, boolean day){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
//获取当前日期字符串
String dateString = sdf.format(new Date());
//取出年、月、日
String[] dateStringArray = dateString.split("-");
StringBuffer folderString = new StringBuffer();
if(year){
folderString.append(dateStringArray[0]);
folderString.append("/");
}
if(month){
folderString.append(dateStringArray[1]);
folderString.append("/");
}
if(day){
folderString.append(dateStringArray[2]);
folderString.append("/");
}
return folderString.toString();
}
}
3.4、mapper层代码
@Mapper
public interface MediaFilesMapper extends BaseMapper<MediaFiles> {
}
4、启动项目,测试
- 使用httpclient测试
### 上传文件
POST http://localhost:9001/upload/coursefile
Content-Type: multipart/form-data; boundary=WebAppBoundary
--WebAppBoundary
Content-Disposition: form-data; name="filedata"; filename="widget-3.jpg"
Content-Type: application/octet-stream
< C:\Users\86152\Pictures\Camera Roll\lyf01.jpeg
- 执行结果
- 查看数据库
- 查看miniO存储目录
上传成功。
结束!!!
hy:11
谎言最大的伤害是让人们不再相信真相。---苏格拉底