多租户架构设计
多租户架构是 SaaS 平台的核心基础设施,它允许多个客户(租户)共享同一套应用和基础设施,同时保证数据隔离与性能公平。本文系统讲解三种多租户隔离模型的实现策略与权衡。
1. 多租户核心概念
1.1 租户定义
**租户(Tenant)**是多租户系统中独立客户单位的抽象,通常对应一个企业客户或组织。每个租户拥有独立的数据、配置、用户和权限。
单租户 vs 多租户:
单租户:每个客户部署一套独立系统
客户 A → [应用 A + 数据库 A + 服务器 A]
客户 B → [应用 B + 数据库 B + 服务器 B]
客户 C → [应用 C + 数据库 C + 服务器 C]
优点:完全隔离、定制灵活
缺点:运维成本高、资源利用率低、升级困难
多租户:所有客户共享一套系统
┌─────────────────────────────────────┐
│ 应用层(共享代码) │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │租户 A │ │租户 B │ │租户 C │ │
│ └──────┘ └──────┘ └──────┘ │
├─────────────────────────────────────┤
│ 数据层(共享/隔离) │
│ 通过租户 ID 区分不同租户数据 │
└─────────────────────────────────────┘
优点:运维简单、成本低、升级一次性
缺点:隔离复杂、定制受限
2. 三种隔离模型
2.1 模型一:共享数据库 + 共享 Schema
所有租户共享同一个数据库和同一套表,通过
tenant_id字段区分数据。
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
tenant_id VARCHAR(64) NOT NULL, -- 租户标识字段
order_no VARCHAR(64) NOT NULL,
amount DECIMAL(18, 2) NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
INDEX idx_tenant_id (tenant_id),
INDEX idx_tenant_order (tenant_id, order_no)
);
-- 查询必须带 tenant_id 条件
SELECT * FROM orders WHERE tenant_id = 'tenant_a' AND order_no = 'O2024001';
架构图:
┌─────────────────────────────────────┐
│ 应用服务(共享) │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │租户 A │ │租户 B │ │租户 C │ │
│ └──────┘ └──────┘ └──────┘ │
└───────────────┬─────────────────────┘
│ SQL + tenant_id
┌───────────────▼─────────────────────┐
│ 数据库(共享) │
│ ┌───────────────────────────────┐ │
│ │ orders 表 │ │
│ │ ┌────────┬──────────┬───────┐│ │
│ │ │tenant_a│ O2024001 │ 100.00││ │
│ │ │tenant_b│ O2024002 │ 200.00││ │
│ │ │tenant_c│ O2024003 │ 300.00││ │
│ │ └────────┴──────────┴───────┘│ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
优点:
- 运维最简单,成本最低
- 新增租户零成本(只需插入 tenant_id)
- 横向扩展容易(只需扩展数据库)
缺点:
- 数据隔离弱(SQL 注入可能跨租户)
- 单租户大数据量影响全表性能
- 备份恢复需过滤 tenant_id
- 复杂查询需全表扫描(除非 tenant_id 在最左前缀)
适用场景:中小型 SaaS、初创产品、租户数据量差异不大。
2.2 模型二:共享数据库 + 独立 Schema
同一数据库实例中为每个租户创建独立的 Schema(命名空间),表结构相同但物理隔离。
-- 租户 A 的 Schema
CREATE SCHEMA tenant_a;
CREATE TABLE tenant_a.orders (...);
-- 租户 B 的 Schema
CREATE SCHEMA tenant_b;
CREATE TABLE tenant_b.orders (...);
-- 切换 Schema
SET search_path TO tenant_a;
SELECT * FROM orders; -- 实际查询 tenant_a.orders
架构图:
┌─────────────────────────────────────┐
│ 应用服务(共享) │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │租户 A │ │租户 B │ │租户 C │ │
│ └──────┘ └──────┘ └──────┘ │
└───────────────┬─────────────────────┘
│ 路由到对应 Schema
┌───────────────▼─────────────────────┐
│ 数据库实例(共享) │
│ ┌───────────────────────────────┐ │
│ │ Schema: tenant_a │ │
│ │ ├─ orders │ │
│ │ ├─ users │ │
│ │ Schema: tenant_b │ │
│ │ ├─ orders │ │
│ │ ├─ users │ │
│ │ Schema: tenant_c │ │
│ │ ├─ orders │ │
│ │ ├─ users │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
优点:
- 逻辑隔离好(Schema 级别)
- 单租户备份恢复简单(导出单个 Schema)
- 可针对单租户优化(索引、统计信息)
缺点:
- Schema 数量有上限(PostgreSQL 无明确限制,MySQL 中 Database 约 10万)
- DDL 变更需遍历所有 Schema
- 连接池管理复杂(动态切换 Schema)
适用场景:中型 SaaS、租户数据量差异较大、有隔离合规要求。
2.3 模型三:独立数据库
每个租户拥有独立的数据库实例,物理上完全隔离。
┌─────────────────────────────────────┐
│ 应用服务(共享) │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │租户 A │ │租户 B │ │租户 C │ │
│ └──────┘ └──────┘ └──────┘ │
└──────┬──────────┬──────────┬────────┘
│ │ │
┌──────▼──┐ ┌────▼───┐ ┌───▼────┐
│ 数据库 A │ │ 数据库 B │ │ 数据库 C │
│(tenant_a)│ │(tenant_b)│ │(tenant_c)│
└─────────┘ └────────┘ └────────┘
优点:
- 最强隔离(物理隔离)
- 最高安全性(租户间完全不可见)
- 可独立备份、恢复、扩容
- 可独立升级数据库版本
缺点:
- 成本最高(每个租户一个实例)
- 运维复杂(监控 N 个实例)
- 资源利用率低(小租户浪费资源)
适用场景:
- 大型企业客户(对隔离性要求极高)
- 合规要求严苛的行业(金融、医疗、政府)
- 高客单价 SaaS(可承受独立数据库成本)
2.4 三种模型对比
| 维度 | 共享 Schema | 独立 Schema | 独立数据库 |
|---|---|---|---|
| 隔离级别 | 应用层 | 数据库逻辑层 | 物理层 |
| 成本 | 最低 | 中等 | 最高 |
| 运维复杂度 | 低 | 中 | 高 |
| 扩展性 | 好 | 中 | 差(单租户扩展) |
| 数据隔离 | 弱(依赖应用) | 中 | 强 |
| 租户上限 | 无限制 | 约 10万 | 受硬件限制 |
| 定制能力 | 差 | 中 | 强 |
| 备份恢复 | 复杂(需过滤) | 中等 | 简单 |
3. 租户路由与上下文传递
3.1 基于子域名的路由
租户 A: https://tenant-a.saas-product.com
租户 B: https://tenant-b.saas-product.com
Nginx 配置:
server {
listen 80;
server_name ~^(?<tenant>[^.]+)\.saas-product\.com$;
location / {
proxy_set_header X-Tenant-ID $tenant;
proxy_pass http://app_backend;
}
}
3.2 基于请求头/参数的路由
HTTP Header: X-Tenant-ID: tenant-a
或 URL 参数: /api/orders?tenantId=tenant-a
或 JWT Claim: { "sub": "user123", "tenant_id": "tenant-a" }
3.3 应用层租户上下文
// ThreadLocal 传递租户上下文
public class TenantContext {
private static final ThreadLocal<String> CURRENT_TENANT = new ThreadLocal<>();
public static void setTenant(String tenantId) {
CURRENT_TENANT.set(tenantId);
}
public static String getTenant() {
return CURRENT_TENANT.get();
}
public static void clear() {
CURRENT_TENANT.remove();
}
}
// AOP / 拦截器中设置
@Aspect
@Component
public class TenantAspect {
@Before("@within(RestController)")
public void setTenantContext(JoinPoint jp) {
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
String tenantId = request.getHeader("X-Tenant-ID");
TenantContext.setTenant(tenantId);
}
}
// MyBatis 拦截器自动添加 tenant_id 条件
@Intercepts(@Signature(type = StatementHandler.class, ...))
public class TenantInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
String tenantId = TenantContext.getTenant();
// 自动改写 SQL,添加 tenant_id = 'xxx' 条件
return invocation.proceed();
}
}
4. 多租户安全与性能
4.1 安全策略
1. 跨租户查询防护
□ 所有数据库查询必须带 tenant_id 条件
□ ORM / 持久层自动注入(不让业务代码控制)
□ 审计所有不带 tenant_id 的查询(告警)
2. 租户间资源隔离
□ 连接池按租户隔离或限制最大连接数
□ CPU/内存资源配额(Cgroup 限制)
□ 存储配额限制(防止单个租户耗尽磁盘)
3. 数据加密
□ 敏感字段加密(AES-256)
□ 可选:按租户独立加密密钥
4.2 性能公平性
| 策略 | 实现 | 目的 |
|---|---|---|
| 限流(Rate Limit) | 令牌桶 / 漏桶,按租户隔离 | 防止单个租户占用全部资源 |
| 连接池隔离 | 每个租户独立连接池 | 防止连接耗尽 |
| 查询超时限制 | 设置单查询最大执行时间 | 防止慢查询影响全系统 |
| 资源配额 | CPU / 存储 / 请求量配额 | 超配额拒绝或降级 |
5. 混合隔离模型
实际 SaaS 平台常采用混合模型:小租户用共享 Schema,大客户用独立 Schema 或独立数据库。
路由层:
租户注册时评估:
数据量 < 1GB → 共享 Schema(池 A)
数据量 1-100GB → 独立 Schema(共享数据库)
数据量 > 100GB 或 企业级 → 独立数据库
租户升级路径:
共享 Schema ──(数据增长)──→ 独立 Schema ──(企业客户)──→ 独立数据库
迁移方式:
在线迁移:双写 → 数据校验 → 切换路由 → 停止旧端写入
6. 总结
多租户架构选择决策树:
租户数量?
├── < 1000 → 独立数据库 或 独立 Schema
├── 1000-100K → 独立 Schema
└── > 100K → 共享 Schema
数据隔离要求?
├── 极高(金融/医疗)→ 独立数据库
└── 普通 → 共享 Schema 或独立 Schema
成本敏感度?
├── 高 → 共享 Schema
└── 低 → 按需求选择
建议:
1. 初创 SaaS 从共享 Schema 起步(最省钱)
2. 预留 Schema/数据库切换能力(代码中抽象 tenant 路由)
3. 混合模型是大 SaaS 平台的最终形态
4. 安全红线:绝不让业务代码决定 tenant_id(框架层强制注入)
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。