对象存储:目录、权限、生命周期
B 端系统上线两年,附件桶攒到 800GB,三件事会同时找上门:预算表上存储费每月稳步上涨;安全团队扫出桶里混着人脸照片,而桶是公共读的;想清理没人要的旧文件,发现压根没有规则在管清理。这三件事是同一个原因:对象存储被当成网盘用了——控制台里点点点建「文件夹」,权限全桶一刀切,生命周期谁也没配。
网盘和对象存储的差别不在容量,在组织方式。网盘的目录是真实结构,对象存储的「目录」只是对象 key 里的一段前缀。这个差别决定两件事的挂法:权限挂在哪一段前缀上,清理规则写到哪个前缀上。
对象存储里没有文件夹
控制台里新建文件夹 tenant-1001/contract/,OSS 里并没有这样一个实体,只是允许你上传一个 key 叫 tenant-1001/contract/a.pdf 的对象——控制台「新建文件夹」实际写入的是一个 0 字节、key 以 / 结尾的占位对象,列对象列表时看得见它,删「目录」时要连它一起删。把 tenant-1001/contract/ 下所有对象删光,那个「文件夹」也跟着消失——它本来就是按前缀列出来的清单。
这带来三个和文件系统不一样的行为。第一,mv 语义是复制加删除:改一个对象的「路径」,实际是读出、写入新 key、删旧 key,文件越大越慢,中途还花双份存储。第二,没有目录层级的通配查询:列目录就是按前缀列 key,前缀越深一层,接口次数越多。第三,也是最有用的一个:在 OSS 里,所有按「目录」配置的东西——权限、生命周期规则、日志、加密——真正生效的维度都是 key 前缀。想清楚前缀怎么定,三件事一起顺。
前缀规划我按两个维度定死,租户和业务类型各占一段:
{tenantId}/{bizType}/{yyyyMM}/{uuid}.{ext}
tenant-1001/contract/202610/9f3a…-e.pdf
tenant-1001/avatar/202610/72bd….jpg

租户在第一段,多租户隔离靠它(上一组文章里 policy 精确 key 的做法,前提就是这个约定);业务类型在第二段,后面权限和生命周期全挂在它身上;年月在第三段,为的是清理和迁移能按时间切;最后一段用 UUID 不用原始文件名,避免重名覆盖和文件名里的路径穿越字符。业务单号不要进 key——单号会变语义(退单、重开),key 一旦写进去就不能改了,改就是上面说的复制加删除。
前缀定了,权限才有的放
桶的读写属性名义上有三档(私有、公共读、公共读写),但云上桶默认开着「阻止公共访问」,公共读写实际上被拦死,B 端系统装着合同和人脸照,可选项只剩私有这一种。桶一旦私有,读取全走预签名 URL(怎么签、有效期怎么定是上一篇的主题),这里只说另一件常被问的事:能不能给不同目录不同的权限,比如客服能读 avatar/ 不能读 contract/?
ACL(Access Control List,挂在桶或单个对象上的访问控制清单)做不到这件事——它只有私有和公共读两档可选,没有按前缀的中间态。对象存储里承担目录级授权的是 Bucket Policy(桶策略,一段 JSON 描述谁对哪个前缀有什么操作权限),按前缀授权正好用上规划好的第二段:
{
"Statement": [{
"Effect": "Allow",
"Action": ["oss:GetObject"],
"Principal": ["acs:ram::1009…:role/customer-service"],
"Resource": ["acs:oss:*:*:my-bucket/tenant-*/avatar/*"]
}]
}
这条策略允许客服角色只读头像前缀。Resource 的格式是 acs:oss:<region>:<账号UID>:<桶名>/<前缀>,两段星号是 region 和账号不限;Principal 填的是 RAM 角色的 ARN(阿里云资源名,acs:ram::<UID>:role/<角色名>,授权给谁就写谁的 ARN,格式错一个字符策略都保存不了)。四个容易踩的点:Action 只给了 GetObject,写文件的其他动作天然被挡住;策略是白名单加黑名单模型,Statement 里的 Effect 填 Allow 或 Deny,显式 Deny 优先于 Allow,出问题先查有没有一条 Deny 挡在前面;通配符的位置决定授权范围——tenant-1001/avatar/* 只放行一个租户的头像,tenant-*/avatar/* 放行全部租户的头像,给客服配后者等于承认这个角色本来就该跨租户,这是策略决定不是笔误;策略改错可以把整个桶锁死,阿里云控制台对错误策略有二次确认,MinIO 上没有这道闸,改策略前先把旧的导出存档。
前缀还有一处容易被当成权限边界:列表接口。给某个前缀签了 List 权限的人,调 ListObjects 能枚举出这个前缀下所有 key——key 里如果有业务单号、手机号这类敏感字段,等于把目录清单给了他。所以 key 里只放非敏感的标识符,敏感信息进对象元数据或业务表,这条和第一节的 UUID 约定是同一个理由的两面。
生命周期省的钱,配错就吐回去
对象存储不会自己把热数据变冷,生命周期规则(Lifecycle,对象存储按时间自动转存储类型或删除对象的规则)就是干这个的:按时间把对象转到更便宜的存储类型或直接删除,规则按前缀配置——OSS 里在桶的基础设置里建生命周期策略、筛选条件选「按前缀」,API 是 PutBucketLifecycle(Filter 指定前缀,Expiration 到期删除,AbortIncompleteMultipartUpload 中止未完成分片)。又一次,前缀规划决定这里好不好用。
账先算明白。按阿里云 0–50TB 档的按量价(2026-10 官网价,以实时价为准):标准 0.12 元/GB/月,低频 0.08,归档 0.033,冷归档 0.015。一个 500GB 的合同附件桶全放标准是 60 元/月;若 80% 的对象 30 天后基本不再被读,配一条规则把它们转低频,这 400GB 变成 32 元/月,加上剩下 100GB 的 12 元,月账单从 60 降到 44。转归档更低,400GB 只要 13.2 元——代价是读取前要先解冻,归档分钟到小时级,冷归档只有解冻一条路且更慢,解冻出的副本在有效期内按标准存储价另算钱;低频还有一笔按 GB 计的数据取回费,读得越多这笔越大,44 元是下限不是上限——又一个只把冷前缀转出去的理由。
省钱的部分就这么多,一个月十几块,在 B 端项目的存储账里不算大头;真正贵的是规则配错之后的三种罚金,每一种都按倍数算钱:
| 配错 | 机制 | 后果 |
|---|---|---|
| 小文件转低频 | 低频/归档按 64KB 最小计量 | 10 万个 5KB 缩略图,实际 0.5GB,按约 6.1GB 计费,费用反而比标准高约 8 倍 |
| 频繁覆盖的对象转低频 | 低频最低存储 30 天(归档 60、冷归档 180),提前删除或覆盖仍按最低天数收 | 头像这类天天被覆盖替换的对象,每一版都按 30 天计费 |
| 热前缀配了归档 | 归档读前要解冻 | 用户在页面里等不到图,客服开始报「附件打不开」 |
第三行的解法不是调时间,是分前缀:常被读的走标准,确认冷的前缀才配转换规则——这就是为什么业务类型要单独占一段前缀,avatar/ 和 contract/ 必须能配不同的规则。转不转的判据看访问分布,不看感觉:OSS 控制台原生的访问监控是桶级的,按前缀要看数据,路径是日志管理里开实时日志查询(或日志转存),对 object 字段按前缀聚合近 30 天的读取次数——近 30 天读取少于个位数次数的前缀才进转换规则,拿不准的先归档不删除,转回来比删了容易。
到期删除(Expiration)比转换更适合大多数 B 端系统:临时区配 7 天删除,碎片(分片上传未 Complete 的残留,上一篇提过它无过期时间、一直计费)单独配一条中止规则。合同类数据不适用自动删除——B 端的数据保留期是合同义务,不是技术参数,这类前缀上什么都别配。
换成 MinIO,差在哪
私有化交付用 MinIO 的,前缀规划和按前缀授权完全一样,三处不一样要说清。MinIO 同样支持生命周期(官方叫 ILM,Information Lifecycle Management),到期删除开箱即用(mc ilm rule add --prefix <前缀> --expire-days <天数>,规则同样按前缀配);但两处要改预期:MinIO 没有独立的「碎片中止」规则可配(阿里云那条 AbortIncompleteMultipartUpload 在 MinIO 上不存在,未完成的分片由服务端自动回收,GitHub 上有 issue 确认这类配置不被保存);存储类型转换要自己先配一个远程层(remote tier,把冷数据转存到另一套 S3 兼容存储或云上),客户内网没有第二套存储时这条路径等于不存在——私有化场景里冷热分层的常见做法是降级成「旧前缀搬迁脚本」,按月把冷前缀的对象搬到备用 MinIO 集群,搬完源端删除。另外 MinIO 的策略模型沿用 AWS IAM 语法,和阿里云的 JSON 字段名不完全一致,别把两家的策略文档混着抄。最后,本地存储的对象在 MinIO 里即时可读,没有归档解冻那套等待语义(转存到 remote tier 的对象首读要走远端取回,有延迟),云上文档里「解冻后读取」的描述在私有化里不存在,交付验收单别照抄云上的条款。
装桶之前对这张清单
五件事,件件对应前面说过的坑:桶私有且读走预签名;租户在第一段、业务类型在第二段,tenant-*/avatar/* 这类策略才写得出来;临时区和业务区分前缀,临时区配到期删除、碎片配中止规则;低频转换只配给大对象前缀,缩略图和频繁覆盖的排除在规则外;合同类前缀不加自动删除,保留期按合同条款定。
前缀定了,权限和清理都挂得上去;前缀乱了,每个新需求都要重排一遍桶。
系列:相关 文件上传:直传、断点续传、秒传各解决什么 | 私有桶的签名 URL 怎么发 | 技术纵深系列索引