多租户不是在数据库里加一个
tenant_id字段。它是一个涉及计算、存储、网络、成本、合规的系统性设计决策。选错隔离模型,轻则一个客户的高流量拖垮所有人,重则数据泄露、合规失败、丢失最大客户。本文不只给出三种隔离模型,更重要的是:在什么时候、基于什么信息,做出哪种选择,以及选错了如何演进。
一、从问题出发:多租户的本质矛盾
多租户的核心矛盾:资源共享带来的成本效益,与租户间相互影响带来的风险,之间的持续张力。
资源共享
├── 好处:降低成本 + 提高利用率 → 商业收益
└── 风险:性能干扰 + 安全风险 → 需要隔离机制
隔离越强 → 成本越高 → 压缩商业利润
(这就是永远不会消失的矛盾)
没有完美解法,只有最符合当前业务阶段的权衡。
二、三种隔离模型:本质、代价与适用场景
2.1 模型示意
Pool 模型(共享池) :所有租户共享同一套数据库,用 tenant_id 字段区分数据。
css
租户 A ─┐
租户 B ─┼──→ 共享数据库(orders 表、users 表都有 tenant_id 字段)
租户 C ─┘
Bridge 模型(桥接):共享同一个数据库实例,但每个租户有独立的 Schema。
css
租户 A ──→ Schema_A ─┐
租户 B ──→ Schema_B ─┼──→ 同一个 MySQL/PG 实例
租户 C ──→ Schema_C ─┘
Silo 模型(独享):每个租户有完全独立的数据库实例。
css
租户 A ──→ 数据库实例 A(独占)
租户 B ──→ 数据库实例 B(独占)
租户 C ──→ 数据库实例 C(独占)
2.2 三种模型对比
| 维度 | Pool | Bridge | Silo |
|---|---|---|---|
| 数据隔离强度 | 逻辑隔离(最弱) | Schema 隔离(中) | 物理隔离(最强) |
| 运营成本 | 最低 | 中 | 随租户数线性增长 |
| Noisy Neighbor 风险 | 高 | 中 | 无 |
| 合规支持 | 困难 | 部分 | 完整 |
| Schema 迁移难度 | 最低(统一执行) | 中(按 Schema 执行) | 高(逐实例执行) |
| 适用客户规模 | 中小租户,数量多 | 中等企业客户 | 大型合规客户 |
2.3 架构师的选择逻辑:不是技术问题,是商业判断
这是绝大多数工程师在租户架构上会犯的错:先选技术方案,后想商业模式。正确的顺序是反过来的。
bash
新客户 / 新需求
↓
【问】客户 ACV(年合同价值)是多少?
→ < $5K → Pool 模型(共享一切,成本最低)
→ $5K -- $50K → 【问】是否有数据隔离合规要求?
→ 无 → Pool 模型
→ 有一般企业要求 → Bridge 模型(独立 Schema,共享实例)
→ > $50K → 【问】是否在金融 / 医疗 / 政府等强监管行业?
→ 否 → Bridge 模型
→ 是(HIPAA / GDPR+)→ Silo 模型(完全独立基础设施)
每种模型的关键注意事项:
Pool → 从第一天起设计好升级路径
Bridge → 连接路由层必须透明,业务代码无感知
Silo → 用 IaC 自动化,否则运维成本失控
行业现实 :2026 年主流 B2B SaaS 同时运营三个层级------Pool 给中小客户,Bridge 给企业级客户,Silo 给头部合规客户。设计时不要选一种,而是设计好从 Pool 升级到 Bridge/Silo 的迁移路径。
三、Noisy Neighbor:从检测到治理的完整链路
3.1 问题是如何发生的
以下是 Noisy Neighbor 最典型的发生路径:
css
① 租户 B 发起全表扫描(COUNT(*) on 亿级表)
② 共享数据库 CPU 飙升到 95%,连接池耗尽
③ 租户 A 的正常查询(INSERT/SELECT)进入等待队列
④ 租户 A 的请求超时(等待连接 > 30s)
⑤ 监控系统检测到 P99 延迟异常
⑥ 租户 A 的用户感知到:应用无响应
⑦ 租户 B 的操作还在继续,毫不知情
关键认知:一个租户的合理(但高消耗)操作,让其他租户无辜受害。这不是 Bug,是共享架构的固有风险,必须在架构层面治理。
3.2 治理栈:每一层都要有租户维度的边界
核心原则:资源共享在哪一层,治理就必须在哪一层。
| 层次 | 治理手段 | 具体配置 |
|---|---|---|
| 接入层(API Gateway) | per-tenant QPS 限流 | 超出 → 429 + Retry-After 响应头 |
| 计算层(K8s) | Namespace ResourceQuota | 每租户设 CPU / Memory 上限 |
| 计算层(K8s) | 线程池隔离 Bulkhead | 大客户独享池,小客户共享池 |
| 数据库层 | 连接池上限 | per-tenant max connections |
| 数据库层 | 查询超时 | 防止长查询霸占连接(建议 30s 上限) |
| 存储层 | Redis ACL | per-tenant keyspace 隔离 |
| 存储层 | MQ per-tenant Topic | 独立消费速率控制 |
| 可观测性 | per-tenant 指标 | tenant_id 作为所有指标的 label |
| 可观测性 | 告警规则 | 单租户占用 > 30% 总资源 → 立即告警 |
3.3 监控是治理的前提
没有 per-tenant 的监控指标,Noisy Neighbor 治理是盲目的。必须在系统设计初期就将 tenant_id 作为所有指标的 label 埋入:
promql
# Prometheus 指标示例
http_requests_total{tenant_id="acme", status="200", endpoint="/api/orders"}
db_query_duration_seconds{tenant_id="acme", operation="select"}
kafka_consumer_lag{tenant_id="acme", topic="task-queue"}
有了这些指标,才能实现:
- 实时发现 Noisy Neighbor(哪个租户的 QPS 突然飙升)
- 证明隔离效果(治理前后对比某租户的资源占比)
- 驱动限额调整(某个租户长期接近上限,是升级套餐的信号)
四、租户生命周期:从创建到迁移
4.1 Onboarding 自动化
新租户注册时,需要自动完成以下所有步骤(缺一不可):
markdown
新租户注册
↓
IaC Pipeline(Terraform / Pulumi)自动执行:
├── 数据库初始化(Pool: 插入租户记录 / Silo: 创建独立实例)
├── K8s 命名空间创建 + ResourceQuota 设置
├── 监控注册(告警规则 + Dashboard 模板)
└── 计费系统注册(计量起始时间)
↓
租户可用(全程无需人工介入)
原则:当租户数量超过 50 个时,任何手动 Onboarding 操作都是不可扩展的。第一个租户就应该用自动化流程创建,否则后续难以补救。
4.2 从 Pool 升级到 Silo:在线迁移的步骤
Shopify 的 Ghostferry 工具是为此场景设计的,核心分三个阶段:
阶段 1:全量复制(用户无感知)
Ghost 工具读取旧 Pod(Pool)全量数据 → 写入新 Pod(Silo)
此期间用户请求继续走旧 Pod,无任何影响
阶段 2:增量追赶(CDC binlog)
旧 Pod 的实时变更(binlog)→ Ghost 工具 → 写入新 Pod
新旧两端数据持续同步,差距越来越小
阶段 3:切换(< 1 秒停写)
① 短暂暂停对该租户的写入(通常 < 1s)
② 确认新旧两端数据完全一致
③ 更新路由表:该租户 → 新 Pod
④ 恢复写入(路由已到新 Pod)
⑤ 验证无误后删除旧 Pod 中该租户的数据
关键点:迁移期间用户无感知的核心是"短暂停写"(通常 <1 秒)+ 路由层热切换,而不是停机迁移。
五、成本边界:让资源消耗可见可控
5.1 成本计量模型
要做到租户成本可见,需要在每一层采集 per-tenant 数据:
| 采集来源 | 采集内容 |
|---|---|
| API Gateway | per-tenant 调用次数 |
| K8s 调度器 | per-tenant CPU-seconds(通过 Namespace 归属) |
| 数据库 | per-tenant 存储量 + 连接时间 |
| 消息队列 | per-tenant 消息数 |
采集后聚合到成本计算引擎,输出三类报告:
- 内部成本报告:识别"亏本客户"(消耗资源远超付费)
- 计费系统:超量收费 / 升档提醒
- 销售支持:"您已使用 80% 配额,升级专业版获得更多"
实践建议 :不需要一步到位做到按量计费。先做到成本可见 (知道每个租户花了多少资源),再逐步推进成本可控 (超出配额时限流),最后实现成本可计费(按用量收费)。
六、架构演进的触发信号
架构师需要知道什么时候该升级租户模型,而不是等到故障发生:
| 信号 | 描述 | 建议行动 |
|---|---|---|
| Noisy Neighbor 投诉 | 某客户投诉性能问题,追查发现是另一租户导致 | 将高消耗租户迁移到 Bridge/Silo |
| 合规审计要求 | 大客户要求提供数据隔离证明 | 为该客户升级到 Silo |
| 成本核算发现异常 | 某个租户消耗了 40% 资源但只付 5% 费用 | 重新定价或升级到隔离套餐 |
| 数据库连接池持续告警 | 连接数经常打满,扩容无效 | 考虑分库或 Bridge 隔离 |
| 单 Shard 存储接近上限 | 某个分片存储已用 70%+ | 提前规划分片扩容或数据归档 |
研究小结
租户架构设计的核心判断:这个客户的商业价值,是否值得承担独占资源的额外成本?
不同价值客户用不同隔离模型,这既是技术架构,也是商业策略。设计时从 Pool 开始,但从第一天起就设计好升级路径------这是成熟 SaaS 平台的标准做法。