当前位置: 首页 > news >正文

【架构实战】对象存储架构:从NAS到OSS的演进

一、NAS存储把我们逼上了绝路

2019年,我们的文件存储用的是NAS(网络附加存储),所有服务器挂载同一个NAS目录。

一开始好好的,但随着文件越来越多,问题开始出现:

  1. NAS单点故障——NAS宕机,所有文件读写失败
  2. 磁盘IO瓶颈——并发上传时NAS扛不住
  3. 扩容困难——NAS容量固定,扩容要停机
  4. CDN无法对接——NAS文件无法直接用CDN加速

后来我们迁移到了对象存储(MinIO/OSS),所有问题迎刃而解。


二、对象存储架构

2.1 对比

┌─────────────────────────────────────────────────────────────────┐ │ 文件存储方案对比 │ │ │ │ 方案 │ 可靠性 │ 性能 │ 扩展性 │ 成本 │ CDN │ │ ───────────────────────────────────────────────────────────── │ │ 本地磁盘 │ 低 │ 高 │ 差 │ 低 │ 不支持 │ │ NAS │ 中 │ 中 │ 差 │ 高 │ 不支持 │ │ MinIO │ 高 │ 高 │ 好 │ 低 │ 支持 │ │ 阿里OSS │ 极高 │ 高 │ 极好 │ 中 │ 原生支持 │ │ AWS S3 │ 极高 │ 高 │ 极好 │ 中 │ 原生支持 │ │ │ └──────────────────────────────────────────────────────────────────┘

三、MinIO实践

3.1 部署

# docker-compose.yml - MinIO集群version:'3'services:minio1:image:minio/minioports:-"9000:9000"-"9001:9001"environment:MINIO_ROOT_USER:adminMINIO_ROOT_PASSWORD:admin123456command:server http://minio{1...4}/data--console-address ":9001"volumes:-./data1:/dataminio2:image:minio/miniocommand:server http://minio{1...4}/data--console-address ":9001"volumes:-./data2:/dataminio3:image:minio/miniocommand:server http://minio{1...4}/data--console-address ":9001"volumes:-./data3:/dataminio4:image:minio/miniocommand:server http://minio{1...4}/data--console-address ":9001"volumes:-./data4:/data

3.2 Java客户端

/** * MinIO文件服务 */@Service@Slf4jpublicclassMinioFileService{@AutowiredprivateMinioClientminioClient;/** * 上传文件 */publicFileUploadResultupload(Stringbucket,StringobjectKey,InputStreamstream,longsize,StringcontentType){try{minioClient.putObject(PutObjectArgs.builder().bucket(bucket).object(objectKey).stream(stream,size,-1).contentType(contentType).build());Stringurl=getFileUrl(bucket,objectKey);returnFileUploadResult.success(objectKey,url);}catch(Exceptione){log.error("文件上传失败: bucket={}, key={}",bucket,objectKey,e);thrownewBusinessException("文件上传失败");}}/** * 下载文件 */publicInputStreamdownload(Stringbucket,StringobjectKey){try{returnminioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(objectKey).build());}catch(Exceptione){log.error("文件下载失败: bucket={}, key={}",bucket,objectKey,e);thrownewBusinessException("文件下载失败");}}/** * 生成预签名URL(临时访问) */publicStringgetPresignedUrl(Stringbucket,StringobjectKey,intexpireMinutes){try{returnminioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucket).object(objectKey).method(Method.GET).expiry(expireMinutes,TimeUnit.MINUTES).build());}catch(Exceptione){log.error("生成预签名URL失败",e);thrownewBusinessException("生成文件访问链接失败");}}/** * 删除文件 */publicvoiddelete(Stringbucket,StringobjectKey){try{minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucket).object(objectKey).build());log.info("文件删除成功: bucket={}, key={}",bucket,objectKey);}catch(Exceptione){log.error("文件删除失败",e);}}}

四、文件管理架构

4.1 文件元数据管理

CREATETABLEfile_info(idBIGINTPRIMARYKEYAUTO_INCREMENT,file_nameVARCHAR(255)NOTNULL,file_typeVARCHAR(50),file_sizeBIGINT,file_md5VARCHAR(32),object_keyVARCHAR(500)NOTNULL,bucketVARCHAR(100)NOTNULL,content_typeVARCHAR(100),business_typeVARCHAR(50),-- avatar/product/documentbusiness_idVARCHAR(100),-- 关联业务IDuser_idBIGINT,create_timeDATETIME,is_deletedTINYINTDEFAULT0,INDEXidx_md5(file_md5),INDEXidx_business(business_type,business_id),INDEXidx_user(user_id));

4.2 文件服务

/** * 文件管理服务 */@Service@Slf4jpublicclassFileManagementService{@AutowiredprivateMinioFileServiceminioService;@AutowiredprivateFileInfoMapperfileInfoMapper;/** * 上传文件(完整流程) */@TransactionalpublicFileUploadResultuploadFile(MultipartFilefile,StringbusinessType,StringbusinessId,LonguserId){// 1. 计算MD5(秒传检查)Stringmd5=calculateMd5(file);FileInfoexisting=fileInfoMapper.selectByMd5(md5);if(existing!=null){// 秒传:创建新引用FileInfonewFile=FileInfo.builder().fileName(file.getOriginalFilename()).fileType(getFileExtension(file.getOriginalFilename())).fileSize(file.getSize()).fileMd5(md5).objectKey(existing.getObjectKey()).bucket(existing.getBucket()).businessType(businessType).businessId(businessId).userId(userId).build();fileInfoMapper.insert(newFile);returnFileUploadResult.quickUpload(newFile.getFileUrl());}// 2. 生成objectKeyStringobjectKey=generateObjectKey(businessType,file.getOriginalFilename());Stringbucket=getBucket(businessType);// 3. 上传到MinIOtry{FileUploadResultresult=minioService.upload(bucket,objectKey,file.getInputStream(),file.getSize(),file.getContentType());// 4. 保存文件信息FileInfofileInfo=FileInfo.builder().fileName(file.getOriginalFilename()).fileType(getFileExtension(file.getOriginalFilename())).fileSize(file.getSize()).fileMd5(md5).objectKey(objectKey).bucket(bucket).contentType(file.getContentType()).businessType(businessType).businessId(businessId).userId(userId).build();fileInfoMapper.insert(fileInfo);returnresult;}catch(IOExceptione){log.error("文件上传异常",e);thrownewBusinessException("文件上传失败");}}}

五、踩坑实录

坑1:Bucket没有设置访问策略

上传的文件无法通过URL直接访问。

解决:设置Bucket为公共读或使用预签名URL。

坑2:文件名中文乱码

上传中文文件名的文件,下载时文件名乱码。

解决:URL编码文件名,设置Content-Disposition。

坑3:存储成本失控

大量过期文件没有清理,存储费用持续增长。

解决:设置文件生命周期,定期清理过期文件。

坑4:图片没有压缩

直接上传原图,5MB的图片加载很慢。

解决:上传时自动压缩,或使用图片处理服务。

坑5:没有防盗链

文件被其他网站直接引用,流量费用暴增。

解决:设置Referer白名单或使用签名URL。


六、总结

对象存储最佳实践:

原则说明
私有Bucket默认私有,需要签名才能访问
预签名URL临时授权访问
秒传MD5去重
生命周期自动清理过期文件
图片处理自动压缩、缩略图
防盗链Referer白名单

血的教训:

文件存储不是"能存就行"。安全、成本、性能,每一个都要考虑清楚。

思考题:你的系统用了什么文件存储方案?


个人观点,仅供参考

http://www.zskr.cn/news/1489677.html

相关文章:

  • RESTfulAPI设计原则与后端实现技巧
  • 2026年口碑好的高师傅漏水检测机构推荐 - mypinpai
  • C++继承与多态进阶实战指南
  • 保姆级避坑指南:用FNL数据从WPS到WRF再到ARWpost的完整流程(附namelist.input配置)
  • 原神帧率解锁完整指南:5步实现144帧极致流畅体验
  • 选购空调家电制冷产品回收加工厂的要点 - 工业品牌热点
  • 工业水处理选购,嘉佰晟环境好不好? - mypinpai
  • SpringBoot自动配置原理深度解析
  • 从黑屏到流畅:在云服务器(AWS EC2 / 腾讯云CVM)上为Ubuntu配置xrdp远程桌面的实战记录
  • 2026年成都风幕机厂家排行:餐饮店风幕机/厂房通风离心风机/商用厨房排烟离心风机/多场景适配实力盘点 - 优质品牌商家
  • 如何快速部署网易云音乐插件管理器:5个专业优化策略指南
  • 锦绣御景花卉的花卉培育周期长吗 - mypinpai
  • OriginPro 2021b保姆级教程:搞定科研论文里的多组数据填充面积图(附数据排列避坑指南)
  • 有资质的建筑垃圾清运,苏园再生 - 工业品牌热点
  • 如何快速解锁网易云音乐:终极NCM文件转换完整指南
  • Kali Linux下Empire 4.2保姆级安装与避坑指南(附常见依赖错误解决)
  • 2026广州搬家公司综合实力TOP5排行榜:服务、价格与售后全维度评测 - 从来都是英雄出少年
  • 告别3D卷积!用Facebook的TimeSformer在单卡上轻松训练长视频模型(附代码实战)
  • Android Fragment - fragment、FragmentContainerView、NavHostFragment、用户 Fragment 之间的关系、Fragment 中隐藏软键盘
  • 搬过5次家才懂!2026广州搬家避坑指南+真正靠谱的5家老牌机构推荐 - 从来都是英雄出少年
  • 构建现代化后端技术栈:拥抱DevOps与自动化部署
  • Android 控件 - ViewPager 的适配器(PagerAdapter、FragmentPagerAdapter、FragmentStatePagerAdapter)
  • RAG实战面试避坑指南:从Demo到系统设计的进阶秘籍
  • 国标权威认证加持|融景科技斩获两项国家级一级行业资质、两项底层自研软著,定义 AI 搜索优化技术标准 - 广东科技观察
  • 保姆级避坑指南:从离线镜像到VSCode调试,搞定gem5 GCN3 Docker环境全流程
  • Python开发进阶之路:掌握高级技巧与最佳实践
  • 聊聊为什么 AI 时代需要一个Token流量网关?
  • 2026年数控折弯机专业定制制造商口碑排名,武嘉数控上榜 - mypinpai
  • 3步搭建私人云游戏服务器:Sunshine游戏串流平台完全指南
  • 2026数控齿轮倒角机实测评测:浙江链笼倒角机、浙江齿条倒角机、齿条磨棱倒角机、数控齿轮倒角机、法兰倒角机、浙江球笼倒角机选择指南 - 优质品牌商家