> 技术文档 > 第5节:分布式文件存储

第5节:分布式文件存储

本节主要是讲解的是分布式文件存储,主要介绍了阿里云OSS云存储和Minio文件存储,本章重点主要是掌握怎么在SpringBoot项目里面接入文件存储。

记录、交流、实践,让每一份付出皆可看见,让你我共同前行😁

1.分布式文件存储高性能高可用讲解

1.1 核心知识介绍

  • 数据存储背景:数据量持续攀升,存储单位从 KB、MB、GB、TB、PB 到 ZB 级别,涵盖图片、文档、素材、静态页面、音视频、安装包等各类文件。
  • 业务应用内存储问题:传统 javaweb 项目文件量增长后,会占用大量内存、磁盘和带宽,无法满足海量请求,存在开发易但扩容难的问题。
  • 分布式文件系统(Distributed File System)
    • 定义:文件系统管理的物理存储资源通过计算机网络与节点相连,或由不同逻辑磁盘分区组合形成层次化文件系统。
    • 特点:自研的分布式文件系统扩容容易,但开发难度大。

1.2 如何保证分布式存储的高性能与高可用?

  • 常见思路:采用副本备份、双活、多活等架构,通过复制协议同步数据到多个存储节点,确保数据一致性,故障时自动切换服务。
  • 性能与高可用的矛盾(基于 CAP 定理)
    • 异步复制:先写一份数据到某机器并立即返回,再异步备份。性能好,但存在容错风险(如未同步时节点宕机导致数据丢失)。
    • 同步多写:同时写多个副本,全部成功后返回。保证数据一致性,但性能受最慢机器影响,性能下降。
  • 选择依据
    • 若要求高性能,可接受偶尔文件丢失或访问出错,选异步复制。
    • 若要求高可用,需保证数据一致性,选同步多写,牺牲部分性能。
  • 类似案例:RocketMQ 消息高可用采用同步双写、异步刷盘策略,即同时写到两个节点内存后返回,再异步持久化到磁盘。

1.3 分布式文件存储业界常见解决方案介绍

解决方案

特点

MinIO

Apache License v2.0 下的对象存储服务器,学习、安装运维简单,支持主流语言客户端整合,可与容器化技术结合,社区活跃但不够成熟,参考资料少

FastDFS

开源轻量级分布式文件系统,客户端少(主要 C 和 java),在互联网创业公司应用较多,无官方文档,社区不活跃,架构和部署复杂,问题定位难

云厂商(阿里云 OSS、七牛云、腾讯云、亚马逊云等)

优点:开发简单,功能强大,易维护(支持不同网络下图片质量、水印、加密、扩容、加速等);缺点:收费,个性化处理和未来转移复杂(部分厂商提供一键迁移)

CDN(Akamai)

在 CDN 领域表现突出

2.Minio

官方网站: MinIO | 企业级高性能对象存储 - MinIO 对象存储

2.1 环境安装

docker run -d -p 9111:9111 -p 9112:9112 --name guslegend_minio \\-e \"MINIO_ROOT_USER=XXX\" \\-e \"MINIO_ROOT_PASSWORD=XXX\" \\-v /dev-ops/minio/data:/data \\-v /dev-ops/minio/config:/root/.minio \\minio/minio:RELEASE.2025-04-22T22-12-26Z server /data --console-address \":9112\" --address \":9111\"

步骤

  1. 访问控制台
  2. 创建 bucket
  3. 上传文件
  4. 预览

总结

MinIO 操作流畅,支持单机和集群部署。对于不能或不使用云厂商存储服务的场景,可自建 MinIO 对象存储集群。

2.2 项目配置

在父文件夹的pom文件中添加maven依赖,并且在用户服务里面也添加。

   io.minio minio 8.5.2 

微服务配置minio

minio: endpoint: http://localhost:9111 accesskey: secretKey: bucketname: 

创建MinioConfig配置类

@Data@ConfigurationProperties(prefix = \"minio\")@Componentpublic class MinioConfig { private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketname;}

2.3 编码实战

service层开发

 @Override public String uploadFileByMinio(MultipartFile file) { try { MinioClient minioClient = MinioClient.builder()  .endpoint(minioConfig.getEndpoint())  .credentials(minioConfig.getAccessKeyId(), minioConfig.getAccessKeySecret())  .build(); //判断桶是否存在 boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(minioConfig.getBucketname()).build()); if (!found) { minioClient.makeBucket(MakeBucketArgs.builder().bucket(minioConfig.getBucketname()).build()); }else { log.info(\"{}桶,存在\",minioConfig.getBucketname()); } //设置存储对象的名称 String folder= String.format(\"%s\",  LocalDateTime.now().format(DateTimeFormatter.ofPattern(\"yyyy/MM/dd\"))); String fileName = CommonUtil.generateUUID(); String newFileName = folder+fileName+file.getOriginalFilename(); PutObjectArgs putObjectArgs = PutObjectArgs.builder()  .bucket(minioConfig.getBucketname())  .stream(file.getInputStream(), file.getSize(),-1)  .object(newFileName)  .build(); minioClient.putObject(putObjectArgs); String imgUrl =minioConfig.getEndpoint()+\"/\"+minioConfig.getBucketname()+\".\"+newFileName; log.info(\"文件上传地址为:{}\",imgUrl); return imgUrl; } catch (Exception e) { log.error(\"文件上传失败:{}\",e); } return null; }

controller层开发

 @PostMapping(\"upload_by_minio\") public JsonData uploadHearImgByMinio(MultipartFile file){ String result = fileService.uploadFileByMinio(file); return result!=null?JsonData.buildSuccess(result):JsonData.buildResult(BizCodeEnum.FILE_UPLOAD_USER_IMG_FAIL); }

3.阿里云OSS

官方网站:对象存储_云存储服务_企业数据管理_存储-阿里云

3.1 项目配置

在父文件夹的pom文件中添加maven依赖,并且在用户服务里面也添加。

   com.aliyun.oss aliyun-sdk-oss 3.10.2 

在用户服务配置配置文件

aliyun: oss: endpoint: XXX access-key-id: XXX access-key-secret: XXX bucketname: XXX

创建OSSConfig配置类

@ConfigurationProperties(prefix = \"aliyun.oss\")@Configuration@Datapublic class OSSConfig { private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName;}

3.2 编码实战

UUID随机生成工具类开发

 /** * UUID * @return */ public static String generateUUID() { return UUID.randomUUID().toString().replace(\"-\", \"\"); }

service层编写

@Slf4j@Servicepublic class FileServiceImpl implements FileService { @Autowired private OSSConfig ossConfig; @Override public String uploadFile(MultipartFile file) { String originalFileName = file.getOriginalFilename(); //相关配置 String endpoint = ossConfig.getEndpoint(); String accessKeyId = ossConfig.getAccessKeyId(); String accessKeySecret = ossConfig.getAccessKeySecret(); String bucketName = ossConfig.getBucketName(); //创建OSS对象 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); LocalDateTime localDateTime = LocalDateTime.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern(\"yyyy/MM/dd\"); String folder = localDateTime.format(formatter); String fileName = CommonUtil.generateUUID(); String extension = originalFileName.substring(originalFileName.lastIndexOf(\".\")); //在OSS创建shop-user文件夹 String newFileName = \"shop-user/\"+folder+\"/\"+fileName+\".\"+extension; try { PutObjectResult result = ossClient.putObject(bucketName, newFileName, file.getInputStream()); //访问路径 if (null!=result){ String imgUrl = \"https://\"+bucketName+\".\"+endpoint+\"/\"+newFileName; return imgUrl; } } catch (Exception e) { log.error(\"头像上传失败\",e); }finally { //关闭OSS对象 ossClient.shutdown(); } return null; }}

controller层编写

@RestController@RequestMapping(\"/api/user/v1\")public class UserController { @Autowired private FileService fileService; /** * 上传用户头像 * @param file * @return */ @PostMapping(\"upload\") public JsonData uploadHearImg(MultipartFile file){ String result = fileService.uploadFile(file); return result!=null?JsonData.buildSuccess(result):JsonData.buildResult(BizCodeEnum.FILE_UPLOAD_USER_IMG_FAIL); }}