核心是围绕隔离性、可扩展性、运维可控性做取舍:租户识别用header或子域名,threadlocal+transmittablethreadlocal透传上下文;数据隔离按业务阶段选独立数据库、schema隔离或租户字段;动态数据源基于abstractroutingdatasource路由;配套租户级限流、配置中心和日志监控。

设计并实现一套多租户 SaaS 系统,核心不是堆技术,而是围绕“隔离性、可扩展性、运维可控性”做取舍和落地。关键不在“能不能做”,而在“在哪一层隔离、怎么传租户上下文、数据怎么不出错”。下面从真实落地角度分四块说清楚。
租户识别与上下文透传要稳准快
租户 ID 必须在请求入口就捕获,并贯穿整个调用链,不能靠每个 service 方法手动传参。
- 推荐用 HTTP Header(如 X-Tenant-ID)或子域名(tenant1.example.com)识别,避免 URL 路径污染业务语义
- 用 Spring 的 OncePerRequestFilter 提前解析并存入 ThreadLocal(如
TenantContextHolder.set(tenantId)),比 AOP 切 Controller 更早、更可靠 - 异步场景(如 @Async 或 MQ 消费)必须显式传递租户 ID,ThreadLocal 不会自动继承;可用 TransmittableThreadLocal 或封装带租户上下文的 CompletableFuture
数据隔离策略选型要看业务阶段和合规要求
没有银弹方案,三种主流方式对应不同成本与风险边界:
- 独立数据库(Database per Tenant):金融、医疗类强合规场景首选。租户间完全物理隔离,DDL 变更互不影响,备份恢复也独立。代价是连接池、监控、迁移成本翻倍
-
共享数据库 + 独立 Schema(Schema per Tenant):中小 SaaS 最常用。PostgreSQL 原生支持,MySQL 需配合
use schema_xxx动态切换;用 AbstractRoutingDataSource + 自定义determineCurrentLookupKey()实现路由 -
共享表 + 租户字段(Row-level isolation):初创期快速上线选它。所有表加
tenant_id字段,配合 Hibernate@Filter全局启用;但必须严防绕过 ORM 的原生 SQL 漏洞,且无法解决跨租户统计类查询性能问题
动态数据源路由是基础设施级能力
不能靠 if-else 手动 new DataSource,而要交由框架统一管理:
- 基于 Spring 的 AbstractRoutingDataSource 实现运行时数据源选择,key 来自
TenantContextHolder.getTenantId() - 连接池建议用 HikariCP,每个租户数据源单独配置 maxPoolSize、connectionTimeout,防止一个租户耗尽连接拖垮全局
- 租户首次访问时懒加载数据源,避免启动时全量初始化;可结合 Nacos 或 Apollo 动态注册新租户配置,触发数据源热加载
配套机制决定能否长期稳定运行
光有数据隔离不够,生产环境还必须补全这几块:
- 租户级限流与熔断:网关层(Spring Cloud Gateway)按 tenant_id 维度配置 RateLimiter,避免某租户突发流量打崩共享服务
-
租户专属配置中心:Apollo 或 Nacos 支持命名空间(namespace)隔离,让每个租户拥有独立的
application-tenant1.yml,控制功能开关、UI 主题、API 白名单等 -
租户感知的日志与监控:Logback 中 MDC 注入 tenant_id,Prometheus metrics 加 label
tenant="xxx",排查问题时能快速过滤归属
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











