P57 项目场景面试:多租户 SaaS 系统怎么设计
面试题:多租户(SaaS)系统怎么设计?多个客户共用一套系统,数据怎么隔离?
1. 三种数据隔离方案
| 方案 | 隔离强度 | 成本 | 适用 |
|---|---|---|---|
| 共享库共享表 + 租户字段 | 弱(逻辑隔离) | 最低 | 中小型 SaaS、免费/低端客户 |
| 共享库独立 Schema | 中 | 中 | 中型客户,隔离与成本均衡 |
| 独立数据库(每租户一库) | 强(物理隔离) | 最高 | 大客户/金融/合规要求高 |
2. 推荐架构(共享库 + 租户字段为主)
text
所有业务表都加 tenant_id
每个请求从登录态/请求头解析出 tenant_id → 塞进上下文(ThreadLocal)
所有 SQL 强制带 tenant_id 条件关键点:
① 租户上下文传递
- 登录后 Token/请求头携带租户标识;
- 用 ThreadLocal 保存当前租户,切面在请求入口设置、finally 清理(防线程池串租户!);
- 下游服务/RPC 也要透传租户 ID。
② 防止"串租户"(越权)
- SQL 层强制:MyBatis 拦截器自动追加
AND tenant_id = ?; - 兜底:数据访问层统一校验租户;
- 测试重点:A 租户不能查到 B 租户数据。
③ 共享资源按租户隔离
- 缓存 key 带租户:
{tenantId}:{bizKey}; - 文件/OSS 按租户目录分;
- 定时任务、队列按租户隔离或标记;
- 配置/套餐按租户维度。
3. 其他设计点
- 租户分级:大客户独立库/Schema,小客户共享,支持平滑升级迁移;
- 数据删除/导出:合规要求租户数据可一键导出/删除;
- 计费:按租户用量(API 调用、存储)计量;
- 性能:tenant_id 加进索引(联合索引第一列或前缀),避免全表扫。
4. 加分点
- 提到"共享表方案要防止索引和查询被租户数据污染"(tenant_id 进索引);
- 说清 ThreadLocal 在线程池中的坑:必须 remove,否则串租户;
- 提到 MyBatis-Plus 有
TenantLineInnerInterceptor可直接实现租户 SQL 改写。
一句话总结
多租户 SaaS 常用"共享库 + tenant_id 逻辑隔离":请求入口解析租户塞 ThreadLocal、SQL 层强制加租户条件、缓存/文件按租户分,再按客户等级提供独立库方案;核心是防串租户和租户数据的强制隔离。