本節主要是講解的是分布式文件存儲,主要介紹了阿里云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"
步驟
- 訪問控制臺
- 創建 bucket
- 上傳文件
- 預覽
總結
MinIO 操作流暢,支持單機和集群部署。對于不能或不使用云廠商存儲服務的場景,可自建 MinIO 對象存儲集群。
2.2 項目配置
在父文件夾的pom文件中添加maven依賴,并且在用戶服務里面也添加。
<!-- Minio各個項目單獨加依賴,根據需要進行添加--><dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.5.2</version></dependency>
微服務配置minio
minio:endpoint: http://localhost:9111accesskey: secretKey: bucketname:
創建MinioConfig配置類
@Data
@ConfigurationProperties(prefix = "minio")
@Component
public class MinioConfig {private String endpoint;private String accessKeyId;private String accessKeySecret;private String bucketname;
}
2.3 編碼實戰
service層開發
@Overridepublic 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依賴,并且在用戶服務里面也添加。
<!-- OSS各個項目單獨加依賴,根據需要進行添加--><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.10.2</version></dependency>
在用戶服務配置配置文件
aliyun:oss:endpoint: XXXaccess-key-id: XXXaccess-key-secret: XXXbucketname: XXX
創建OSSConfig配置類
@ConfigurationProperties(prefix = "aliyun.oss")
@Configuration
@Data
public 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
@Service
public class FileServiceImpl implements FileService {@Autowiredprivate OSSConfig ossConfig;@Overridepublic 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 {@Autowiredprivate 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);}
}