
上周帮一家公司做分布式存储治理数据仓库、算法平台、BI报表三条业务线共用一个集群。月底对账的时候两边数据团队吵得不可开交算法组凌晨的批量任务把带宽占满数据仓库的贴源同步被拖到天亮BI组临时跑了一个全表扫描直接把对象存储网关打到超时最后分摊账单的时候谁都不承认自己用了那么多容量因为没有一套按租户计量的数据。这类问题在“大数据”领域里太常见了。分布式存储一旦支撑多个业务团队就一定会面对多租户的诉求。你不可能每次都物理拆集群成本不允许、运维也不允许。靠谱的做法是在共享的存储集群上叠加多租户支持方案从元数据、容量、QoS、权限、账单五个维度把隔离和治理做起来。这篇文章我想把这些年落地的方案、踩过的坑、还有选型时的思考完整梳理一遍给正在做数据平台治理的朋友一个可以直接参考的框架。1. 刚需来自哪里资源抢占、越权访问和费用扯皮多租户这三个字听起来像是云厂商的文档里才会出现的词但只要你所在的公司有两条以上业务线共用一个存储集群你已经在做多租户了只是做得比较原始而已。1.1 三个典型场景我见的比较多的是这三种情况。第一个是资源抢占。某条业务线的离线任务深夜跑批MapReduce 或者 Spark 的 shuffle 数据量巨大把 HDFS 的带宽、磁盘 IO 全部吃满。其他业务线第二天早上来查数发现凌晨的任务还在排队或者作业重试了七八次才成功。这种“一个租户拖垮一群人”的现象本质上就是缺少容量与IO的隔离能力。第二个是越权访问。AB两个团队共用一个目录前缀或者一个桶里塞了所有团队的数据。默认权限是“所有人可读”有些敏感的离线表被误拷贝、误导出。更麻烦的是删除操作——有人用hdfs dfs -rm -r删错了路径把另一个团队还没备份的中间结果表清掉了。事后追责发现谁也说不清谁有权限。第三个是费用扯皮。部门要做成本分摊问存储团队每个业务方用了多少TB、产生了多少次请求。存储团队只能靠人工统计从 NameNode 的 fsimage 里 parsing或者从对象存储网关日志里 awk折腾几天给出一份Excel大家还都不认。这三个场景可能你都遇到过。它们有一个共同点问题不是存储本身坏了而是缺少租户边界。1.2 多租户方案要解决的四个问题我自己做方案时会把需求收敛成四个目标。隔离租户A的数据租户B不能读更不能写租户A的任务不能把租户B的IO资源挤垮。限额每个租户的容量、文件数、请求速率有明确上限超限后可以拒绝或排队。优先级在共享集群上关键业务可以抢占更多带宽或队列资源普通业务在高峰期让路。可审计谁在什么时间访问了哪个租户的数据容量和请求量是否能按租户计量账单是否可追溯。如果你的方案把这四件事都覆盖了多租户基本就立住了。后面我会按这个框架展开每一层的落地细节包括元数据设计、配额与限流、权限体系、存储引擎选型、以及运维侧的计量闭环。2. 第一道墙元数据与命名空间的租户隔离多租户的第一道设计发生在元数据层。你得先回答一个问题租户的边界画在哪里。在文件系统里它通常是目录或命名空间在对象存储里通常是桶或前缀。2.1 共享集群里的命名空间划分HDFS 场景下最朴素的做法是/user/tenant_id或/tenant/tenant_id路径隔离。每个租户只能访问自己目录下的数据这个通过 HDFS 的 ACL 和 Ranger 策略来控制。路径设计上我有个习惯不要在租户目录下平铺所有业务表按层级设计成/tenant_v1/{tenant_id}/lake/{层}/...。比如/tenant_v1/rec/warehouse/ods/xxx。这样有两个好处一是后续做数据生命周期管理冷热分层、归档规则可以直接按树路径匹配二是配额可以下放到子树粒度某些重要目录可以单独调大配额。对象存储场景下一般有两种划分方式一个租户一个桶bucket per tenant或者一个共享桶加前缀prefix per tenant。桶级别隔离性好权限、生命周期、跨区域复制都能单独配置审计日志也清晰。缺点是如果租户数量很大比如几千上万桶数量可能会成为限流瓶颈而且部分网关对小桶数的性能优化更充分。前缀级别成本低元数据规模小但权限控制粒度变粗S3 的 ListObjects 性能也容易受影响。我的推荐是如果租户数量在几十个以内优先桶级别如果是数据产品开放平台这种海量小租户场景前缀级别更务实但必须配合 IAM 策略做前缀级权限。2.2 全局唯一租户标识与数据模型设计命名空间定完更重要的是元数据建模。很多团队只做了路径隔离但所有元数据表里没有租户字段后面做计量和权限就会非常痛苦。我在设计统一元数据模型时至少会包含这几个字段tenant_id租户全局唯一标识例如rec、growth、adsnamespace_type命名空间类型是hdfs、s3bucket还是s3prefixnamespace_value具体路径或桶名quota_capacity容量配额quota_file_count文件数配额owner_team归属团队status启用、冻结还是删除。这样一张表既能支撑“有哪些租户”的查询也能在扩容的时候按租户维度统计容量增长趋势。更重要的是后面接 Ranger 权限策略、Prometheus 监控打点、账单分摊全部都以tenant_id作为主键就不会各个系统各叫各的。2.3 配额引擎目录级与桶级双视角元数据层还有一个关键部分是配额。HDFS 本身提供两类配额space quota空间限额和quota文件、目录数量限额。Command 很直接# 给租户目录设置 10TB 空间上限 hdfs dfsadmin -setSpaceQuota 10t /tenant_v1/rec # 给租户目录设置 100 万个文件数上限 hdfs dfsadmin -setQuota 1000000 /tenant_v1/rec我强烈建议两个配额都设置。只设空间配额不设文件数配额的坑后面我会专门讲这里先留个悬念。对象存储的配额实现要看网关。MinIO 里有mc admin bucket quotaCeph RGW 可以借助rgw_quota配置桶容量上限云上对象存储通常直接配置 Bucket Quota。值得注意的是Ceph RGW 的配额是周期性检查默认是每次写操作后异步刷新存在一定延迟所以配额不是实时硬限制这个在服务承诺里要讲清楚。3. 第二道墙容量配额与IO限流的落地细节元数据隔离负责“数据归属”但资源隔离才是多租户真正开始起作用的环节。没有容量和IO限制一个租户的突刺流量仍然会拖垮整个集群。3.1 容量层池、目录、桶的配额组合容量隔离可以分为两级。集群级在 HDFS 里把一条副本链路比如3副本的集群按租户拆成不同的存储池逻辑上叫“租户池”。每个池可以配独立的容量上限和扩容策略。Ceph 里对应的是 pool 隔离不同租户的数据落在不同 pool可以设置独立的target_max_bytes。这种方式的隔离效果最好缺点是运维成本高池间数据迁移很麻烦。目录/桶级在共享一个池的前提下用配额限制每个租户能使用的容量和对象数量。优点是灵活缺点是某些底层组件不感知租户边界磁盘满的时候是全局满而不是某个租户满。我实际执行的时候一般两层都做关键租户比如在线推荐服务分配独立池普通分析型租户共享大池但每个租户挂容量配额。这是成本与隔离效果之间的平衡点。3.2 IO层客户端限流与优先级容量配额管的是“存多少”IO限流管的是“读写多快”。这个层面各组件差别很大。HDFS 侧NameNode 的 RPC 队列可以按租户做公平调度。HDFS 自带的dfs.namenode.faircallqueue支持给不同调用者配置权重caller维度可以把关键租户的 RPC 请求优先级提高。数据节点侧可以采用dfs.datanode.fsdataset.quota配合 io 加权。再上层YARN 的 Capacity Scheduler/Fair Scheduler 也是多租户调度的一部分因为存储的读写压力通常来自计算任务队列隔离能从源头上限制“谁可以同时跑多少任务”。对象存储场景下网关层做限流是最直接的方式。我在 MinIO 网关后面会加一层限流中间件按tenant_idoperation设置令牌桶参数。比如租户A的 GET 速率 5000 QPSPUT 速率 1000 QPS超限直接返回503 SlowDown。云上对象存储则直接用 IAM 策略里的Condition配合s3:RequestRateLimit这类条件键或者直接买 QoS 套餐。3.3 部署形态选择独立集群、联邦还是共享数据面在讲方案选型的时候我们经常要先做一个层面上的决策到底是给每个租户物理独立集群还是做逻辑多租户。独立集群租户数少、预算充足时最省心运维隔离彻底故障爆炸半径小。但资源利用率低每个集群都要留 buffer机器成本可能放大30%~50%。HDFS Router-based Federation多个独立命名空间共享一组 DataNode。适合需要强隔离元数据、又想共享存储资源的场景。共享集群逻辑多租户这是最常用的形态。一个集群所有租户共享靠上层的命名空间、配额、权限、调度器来划边界。如果业务规模不大几十个节点以内我建议直接共享集群逻辑多租户把重心放在后面几层的治理上。等到租户间的物理资源诉求冲突到不可调和再按域拆集群也不迟。4. 数据安全不能只靠存储ACL打通行列级权限多租户的“隔离”如果只做到目录和桶级别那只能防住“手滑”防不住“越权查询”。现实中的敏感数据都在表里一张大宽表既有敏感列也混着多个租户的行。这时候存储层的 ACL 根本不够用必须往上打通到计算引擎的行列级权限。4.1 存储层的ACL能做什么、不能做什么HDFS ACL 可以控制某租户能否读/写某个目录S3 Bucket Policy 可以控制某个 IAM 角色能否 GetObject。这解决的是“这块数据属于谁”的问题。但一张业务宽表如果拿租户的账号去查它能读整个表。表里面可能有一列手机号、一列用户行为埋点、一列广告点击流。某些行是这个租户负责的某些行是另一个租户的。存储层完全不理解表和行的语义。这是计算层和存储层的分工决定的存储层只认块和文件不认行和列。4.2 用Ranger打通存储与SQL引擎的行列权限现在业界比较成熟的做法是在统一权限服务里定义策略然后让存储和计算引擎都去这个服务拿策略。Apache Ranger 是我用过最多的选择。它通过插件机制支持 HDFS、Hive、HBase、Kafka也支持 Presto/Trino 这类 SQL 引擎。可以在 Ranger 里给某个租户的角色配置行级过滤比如广告分析组查询广告点击表只允许看到tenant_id ads的行列级脱敏比如增长分析组默认看不到完整的手机号只能看到mask_hash(phone)的结果列级权限比如某张财务表只授权amount列其他列即使 SELECT * 也报权限不足。SQL 引擎在执行计划生成阶段就会把 Ranger 的过滤条件下推到存储扫描层所以数据并不是被拉出来之后才过滤的而是在扫描时就被裁剪掉了。这一点很重要它既保证了性能也保证了安全——租户的查询请求从引擎侧就没有机会触摸到不属于它的数据。如果你还在用 Hive 并希望轻量实现Hive 的 Row Filter 和 Column Masking 也可以通过配置hive.security.authorization.manager配合 Ranger 一起使用。开源社区里也有几个行/列权限项目比如 Apache Arrow 生态里的权限推演方案但稳定性和社区成熟度还是 Ranger 更好。4.3 动态脱敏与审计行列权限之外脱敏策略也要纳入多租户设计。A团队做用户增长分析它需要看到“某个区域有多少用户”但不需要知道“某个用户是谁”。如果把手机号直接明文查出去出了问题就是安全事故。Ranger 里可以配 Masking Policy-- 示例对 ad_profile 表的 mobile 列非 owner 角色只能看到后四位 CREATE MASKING POLICY mask_mobile ON ad_profile FOR ROLE analyst_growth COLUMN mobile USING (***-****- || SUBSTR(mobile, 8, 4));配合审计日志每次跨租户访问、每次脱敏字段的查询都有一个 trace。这样一旦出现安全问题你能快速定位到人、到请求、到SQL。审计日志建议直接进统一的日志平台留存至少180天因为数据泄露事故的追溯窗口通常比你想的长得多。5. 主流存储引擎的租户化改造路线怎么选不同分布式存储组件多租户能力的侧重点不一样。选型时不能听大家说“XX支持多租户”就完事要拆开看它到底在哪一层支持。5.1 对象存储路线的租户设计MinIO、Ceph RGW、OpenStack Swift 是自建对象存储的三巨头。MinIO 的多租户最平易近人一个 bucket 天然就是一个隔离单元配上 IAM Policy 可以做到用户/组级别的精细权限。容量配额可以通过mc admin bucket quota配置。限流层面依靠网关扩展简单场景自己写个 Nginx 限流模块就够。Ceph RGW 的多租户能力更底层支持租户tenant概念每个租户有独立的用户空间和密钥桶名可以在不同租户间重复。存储池可以和租户绑定配额按用户或桶设置QoS 通过 dmclock 调度器配置 reservation/limit/weight。它的隔离深度更适合大规模多域场景但配置复杂度和学习门槛也更高。5.2 文件系统/HDFS路线的租户设计HDFS 这个老牌组件多租户能力靠组合拳命名空间目录隔离 ViewFS 挂载表容量dfsadmin 的 spaceQuota / quota权限ACL Ranger调度NameNode 的 FairCallQueue YARN 队列元数据扩展Router-based Federation 按租户拆分 NameNode。如果你们的数据栈是 Spark/Hive 体系HDFS 这条路线是最成熟的因为它和计算引擎的集成最深。缺点是运维起来真的繁琐尤其是 Federation 模式要在多个 NameNode 之间做挂载表同步踩坑概率不低。5.3 选型对照表维度MinIOCeph RGWHDFS是否需要独立网关是是否直接访问NameNode租户最小隔离单元BucketTenantBucket目录/命名空间内置容量配额支持支持支持内置读限流弱靠网关层支持dmclock可调靠 RPC 队列优先级行列级安全不涉及不涉及通过Ranger打通推荐场景中大型数据平台S3接口友好大规模对象存储多域强隔离离线数仓、数据湖运维复杂度低中高这张表不是要你照抄而是提醒你选型之前先明确当下最主要的租户诉求是“容量隔离”“权限隔离”还是“QoS隔离”。需求决定技术选型这一点永远优先。6. 真实改造案例视频画像平台的三租户落地前面讲了一堆原理下面用一个我经手的案例把整个落地过程过一遍。这个案例是一家视频平台存储层使用共享HDFS集群 对象存储网关业务分三个租户推荐算法组rec、增长分析组growth、广告投放组ads。6.1 现状与目标改造前12个节点的 HDFS 集群三个租户的数据全部堆在/data下没有配额、没有限流、没有租户级监控。一次广告投放组的 impala 查询把磁盘IO打满推荐组的在线特征更新延迟从平均60ms飙到800ms线上推荐效果当场跳水。这次改造目标很明确每个租户独立目录独立容量配额与文件数配额通过 Ranger 配置行级过滤和列级脱敏对象存储网关按租户限流上线租户级监控看板。6.2 一次性落地步骤第一步划分命名空间与目录。hdfs dfs -mkdir -p /tenant_v1/rec/lake/warehouse hdfs dfs -mkdir -p /tenant_v1/growth/lake/warehouse hdfs dfs -mkdir -p /tenant_v1/ads/lake/warehouse然后把已有数据迁移到租户目录迁移用 distcp并建议重新设置副本数为标准配置。数据迁移在共享集群上做最好在非高峰期执行并且给 distcp 加上限速参数hadoop distcp -D mapreduce.map.memory.mb512 \ -D fs.s3n.throughput.max.mbpersec200 \ hdfs://old-cluster/data/batch_xxx \ hdfs://new-cluster/tenant_v1/rec/lake/warehouse/batch_xxx第二步设置配额。hdfs dfsadmin -setSpaceQuota 20t /tenant_v1/rec hdfs dfsadmin -setQuota 2000000 /tenant_v1/rec hdfs dfsadmin -setSpaceQuota 8t /tenant_v1/growth hdfs dfsadmin -setQuota 800000 /tenant_v1/growth第三步给三个租户建 Ranger 策略。拿广告投放组举例它只能读ads租户目录下的表且手机号自动脱敏。策略建好后用租户账号提交一个查询验证SELECT * FROM ads_events LIMIT 10; -- 期望结果非ads租户查询被拒绝ads租户查询正常mobile字段显示掩码。第四步对象存储网关限流配置。我用的是 MinIO 自研限流中间件按访问密钥前缀识别租户给每个租户一个令牌桶参数。配置文件长这样rate_limits: - tenant: rec get_qps: 8000 put_qps: 2000 list_qps: 500 - tenant: growth get_qps: 2000 put_qps: 800 list_qps: 300第五步监控与计量。在 Prometheus 里给存储指标统一加上tenant_id标签并按租户维度做 Recording Rules。后面细说。6.3 压测与验收结果改造完成后我们模拟了三种场景。场景一ads 租户发起高并发全表扫描同时 rec 租户做在线特征更新。改造前rec 特征更新 P99 延迟 800ms改造后因为网关限流把 ads 的请求挡在门口rec 的 P99 稳定在 110ms。场景二growth 租户试图写入超过配额的容量。写入在达到 8TB 硬顶后被拒绝不占用 rec 租户的容量空间。场景三growth 租户账号查询含手机号的表。Ranger 列脱敏生效结果里手机号为掩码格式。三个场景都达到预期。最终月度账单分摊从人工统计3天缩短到看板2小时各租户资源消费量第一次有了无争议的数据依据。7. 监控、成本分摊与租户治理的运营闭环多租户方案不是螺丝刀拧完就能走人的它需要一套运营体系持续喂数据。监控和成本分摊是让多租户能长期存活的两个轮子。7.1 按租户打点标签设计从第一天起所有监控指标都要带上租户标签。包括但不限于容量指标{tenant_id, namespace_type, bucket}维度的总容量、已用容量、文件数请求指标{tenant_id, operation, status}维度的请求量、成功率、延迟分位数限流指标{tenant_id, throttle_reason}维度的被限流次数。Prometheus 的标签设计要克制基数不能爆炸。租户数量几十个时没问题如果几万个就要谨慎使用高基数标签。一般我会用tenant_id service storage_type三个固定标签避免把请求参数捞进标签里。有了这些指标就可以做租户维度的 Recording Rulegroups: - name: tenant_storage.rules rules: - record: tenant:storage_bytes:total expr: sum by(tenant_id) (max_over_time(hdfs_space_used_bytes{storage_typehdfs}[24h])) - record: tenant:s3_get_requests:total expr: sum by(tenant_id) (rate(s3_request_total{operationget, status2xx}[5m]))7.2 成本计量模型很多团队做多租户卡在“存储费率怎么算”。其实不需要精确到每一分钱关键是口径统一。我常用的计量模型月存储费 容量用量(GB) × 每GB月单价 × 副本系数月请求费 GET请求量 × 单价 PUT请求量 × 单价 LIST请求量 × 单价租户月账单 月存储费 月请求费 跨域流量费如有注意副本系数。HDFS 默认3副本那么租户用了 1GB 逻辑数据实际占用 3GB 物理空间。成本单价也该按物理空间算否则账单和磁盘采购对不上。如果某一层做了 EC 纠删码比如 1.5 副本等价开销就按实际冗余倍数算。有了这些口径就可以在数据大屏上按租户展示月度资源消费趋势也就是俗称的“成本大屏”。这块很有用它能把存储团队从“背锅位”变成“数据支撑位”让各个业务团队看到自己行为导致的成本变化。7.3 日常巡检与治理例会最后运营流程要固化。我建议每月做一次存储多租户巡检重点看几件事是否有租户容量使用率超过 85%需要提前扩容或提示清理是否有租户小时级突发流量导致其他租户延迟劣化是否有超大表或大量小文件异常增长是否有跨租户的非预期访问记录。巡检数据直接来自第7.1节攒的指标不用临时写脚本。治理例会就摆数据说话谁涨了、谁超了、谁被限流了一目了然。8. 几个让我印象深刻的坑与避坑思路最后这部分我想聊几个踩过的坑。这些都是线上事故换来的经验希望大家不要重复交学费。8.1 只配容量配额没配文件数配额有一段时间某个租户的数据导入任务经常写几百万个小文件单个文件只有几十KB。空间没超但 NameNode 的内存被打爆了整个集群的 RPC 上锁所有租户都受影响。HDFS 的setSpaceQuota管空间setQuota管文件数。两个一定要同时配。文件数配额还有一个隐藏作用它会倒逼业务方做小文件合并长期看对集群健康度帮助很大。8.2 用本地磁盘quota代替存储层配额早期我在某个项目里试图用 Linux 的磁盘配额比如setquota来限制某个租户目录的容量。结果发现完全不好使一个租户的数据分散在多台机器的多个磁盘上本机配额只能管本机租户换一台机器写又不受限制。而且 Linux 本地quota对目录层级不敏感跨挂载点还会失效。分布式存储的容量配额一定要走存储系统自身的配额机制不要另辟蹊径。8.3 恢复任务不带租户标签有一次做灾备恢复演练运维同学从备份集群恢复数据直接用distcp把数据恢复到/data_restore/下而不是恢复到对应的租户目录。结果恢复出来的数据既没挂权限也没挂配额还被计到了“无主租户”名下。查了半天才定位到是恢复流程没带tenant_id映射。后来我在恢复流程里加了一道校验恢复任务执行前先检查目标路径是否属于某个已注册的租户目录如果目标路径不在租户目录清单里直接拒绝执行。这是成本极低但非常有效的安全网。8.4 只限外部流量不限内部流量一开始做对象存储网关限流时我只限制了公网/外部 API 的入口。结果内部的数据同步作业从对象存储跨域复制、ETL抽取跑起来照样把集群内部带宽打满。那段时间经常出现“外部流量正常内部延迟却飙高”的诡异现象。后来我把限流拆成“南北向”和“东西向”两层外部 API 入口一套限流内部数据传输链路单独一套带宽限制。多租户隔离不仅要挡住外部抢资源也要挡住内部作业自己人的无序竞争。8.5 最后的建议做多租户方案别一上来就追求云厂商级别的完善度。先明确需求边界当前最痛的是容量、权限、还是成本分摊先把最痛的点用最小的动作解决掉再逐步叠加能力。我在多个公司验证过的路径是目录/桶划分 → 容量与文件数配额 → 行列级权限与脱敏 → 网关/调度限流 → 租户级监控与账单。每一步解决一个具体问题每解决一个存储团队的日子就好过一点。数据平台的多租户本质上不是某个组件的一个开关而是一套贯穿元数据、存储、计算、权限、运维的治理体系。你能把“租户”这个概念真正落地成组织内的共识比任何技术细节都重要。