2. 多租户架构基础:多租户概念、隔离模式与租户上下文管理
2.1 多租户核心概念
多租户(Multi-Tenancy)是一种软件架构模式,允许单个应用实例同时服务多个租户,每个租户拥有独立的逻辑视图与数据边界。在计费模型中,租户通常对应一个企业客户或组织,其内部的用户、订阅、用量与账单数据必须严格隔离。
关键定义: 租户 ≠ 用户。一个租户包含多个用户,共享同一套计费策略、定价计划与资源配额。
| 术语 | 说明 | 计费场景示例 |
|---|---|---|
| 租户 (Tenant) | 独立的组织或客户实体 | 某企业客户“Acme Corp” |
| 租户管理员 | 拥有租户级管理权限的用户 | 可查看本租户账单、修改订阅 |
| 租户用户 | 隶属于租户的普通用户 | 消耗 API 调用次数,触发计费事件 |
| 租户上下文 | 标识当前请求所属租户的信息载体 | JWT 中的 tenant_id 声明 |
2.2 三种隔离模式对比
选择隔离模式直接影响计费数据的安全性、查询性能与运维复杂度。以下是三种主流模式的详细对比:
| 隔离模式 | 数据存储方式 | 隔离强度 | 计费场景适用性 | 运维成本 |
|---|---|---|---|---|
| 数据库隔离 | 每个租户独占一个物理/虚拟数据库 | 最高 | 金融级计费、合规要求严格的行业 | 高(备份、迁移、连接管理) |
| Schema 隔离 | 共享数据库,每个租户独占一个 Schema | 高 | 中大型 SaaS,需要独立表结构扩展 | 中(Schema 迁移需逐租户执行) |
| 表隔离 | 共享数据库与 Schema,通过 tenant_id 列区分 |
低 | 标准化计费模型,租户量级大(万级) | 低(需防范 SQL 注入与越权) |
架构建议: 计费系统的核心表(如
billing_invoices、usage_records)建议采用表隔离 + 强制租户过滤,配合数据库级 Row-Level Security(RLS)策略,兼顾扩展性与安全性。
2.3 隔离模式架构图
下图展示了三种隔离模式在数据库层的物理与逻辑布局:
2.4 租户上下文管理
租户上下文是贯穿整个请求链路的身份标识。在计费接口中,每一次用量上报、账单查询都必须携带正确的租户上下文,否则会导致数据泄露或计费错误。
2.4.1 上下文传递链路
- 客户端请求:在 HTTP Header 中携带
X-Tenant-ID或 JWT 中的tenant_id声明。 - API 网关/反向代理:解析并校验租户 ID 的合法性,拒绝无效或格式错误的请求。
- 鉴权拦截器:从请求中提取租户上下文,注入到当前线程的
TenantContext对象中。 - 业务服务层:从
TenantContext获取租户 ID,用于数据查询过滤、计费策略路由。 - 数据访问层:在 SQL 查询中自动追加
WHERE tenant_id = ?条件,或通过 RLS 策略强制过滤。
常见陷阱: 异步任务(如消息队列、定时任务)中容易丢失租户上下文。解决方案是在消息体中显式携带
tenant_id,并在消费端重新绑定上下文。
2.4.2 上下文管理代码示例
以下是一个基于 ThreadLocal 的租户上下文管理实现(Java 示例):
public class TenantContext {
private static final ThreadLocal<String> CURRENT_TENANT = new ThreadLocal<>();
public static void setTenantId(String tenantId) {
if (tenantId == null || tenantId.isBlank()) {
throw new IllegalArgumentException("tenantId must not be null or blank");
}
CURRENT_TENANT.set(tenantId);
}
public static String getTenantId() {
String tenantId = CURRENT_TENANT.get();
if (tenantId == null) {
throw new IllegalStateException("Tenant context not set for current thread");
}
return tenantId;
}
public static void clear() {
CURRENT_TENANT.remove();
}
}
2.4.3 上下文传递流程图
下图展示了从 HTTP 请求到数据库查询的完整租户上下文传递链路:
2.5 隔离模式选型决策表
在计费系统设计阶段,可参考以下决策矩阵选择隔离模式:
| 决策条件 | 推荐模式 | 理由 |
|---|---|---|
| 租户数量 < 100,合规要求极高 | 数据库隔离 | 物理隔离,审计日志独立,满足 SOC2/PCI-DSS |
| 租户数量 100~1000,需要定制表结构 | Schema 隔离 | 逻辑隔离,支持按租户扩展字段 |
| 租户数量 > 1000,计费模型标准化 | 表隔离 + RLS | 运维成本低,水平扩展容易 |
| 混合场景(部分大租户 + 大量小租户) | 混合模式 | 大租户独享数据库,小租户共享表 |
实践要点: 无论选择哪种隔离模式,租户上下文必须在所有计费接口中强制校验。建议在 API 网关层统一注入
tenant_id,并在业务层通过 AOP 或中间件自动绑定,避免开发人员遗漏。