Java 多租户系统设计实战:构建支持隔离、安全、高扩展的 SaaS 架构模型
多租户(Multi-Tenancy)是指一个系统在同一实例下为多个客户(即“租户”)提供服务,数据、配置和功能彼此隔离,系统维护统一。java复制编辑@Componentpublic class TenantFilter extends OncePerRequestFilter {protected void doFilterInternal(HttpServletRequest request,
随着 SaaS 模式在各行业的普及,越来越多的企业在构建产品平台时会面临“一个系统服务多个客户(租户)”的需求。为此,多租户(Multi-Tenant)架构成为后端系统设计的重要议题。本文将深入探讨多租户系统的实现原理、常见模型、数据库隔离方式、安全风险控制,并以 Java + Spring Boot 实现一个完整的多租户支持架构。
一、什么是多租户系统?
1.1 多租户定义
多租户(Multi-Tenancy)是指一个系统在同一实例下为多个客户(即“租户”)提供服务,数据、配置和功能彼此隔离,系统维护统一。
1.2 多租户与多实例对比
| 特性 | 多租户架构 | 多实例部署 |
|---|---|---|
| 资源利用率 | 高 | 低 |
| 成本 | 低 | 高 |
| 数据隔离 | 中(逻辑隔离) | 高(物理隔离) |
| 运维复杂度 | 低 | 高 |
| 扩展能力 | 高 | 一般 |
二、多租户实现模型对比
2.1 数据库隔离模型
| 模型 | 描述 | 隔离性 | 扩展性 | 成本 |
|---|---|---|---|---|
| 独立数据库 | 每个租户使用独立数据库 | 高 | 中 | 高 |
| 同库不同 schema | 共用数据库,不同 schema 隔离 | 中高 | 中 | 中 |
| 同库同表加租户 ID | 所有租户共享数据库与表,通过租户 ID 区分 | 低 | 高 | 低 |
2.2 选型建议
-
大型客户(政企):使用“独立数据库”或 schema 模式,保障数据物理隔离
-
中小客户 / 初创产品:使用“同表加租户 ID”模式,开发与运维简单
三、租户标识获取与传递机制
3.1 传递方式
| 方式 | 示例 | 说明 |
|---|---|---|
| 请求 Header | X-Tenant-ID: abc123 |
推荐方式,安全性高 |
| Token 中嵌入 | JWT 中包含 tenant_id 字段 | 鉴权同时传递租户信息 |
| 子域名 | tenant1.example.com |
需配置 DNS |
| URL 路径 | /tenant1/api/data |
可能造成路由复杂 |
3.2 建议实践
-
所有请求需携带租户标识
-
网关统一提取并下发租户上下文信息
-
后端服务统一读取租户上下文用于数据隔离
四、Spring Boot 中实现多租户支持
4.1 使用 ThreadLocal 管理租户上下文
java复制编辑public class TenantContext { private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>(); public static void setTenant(String tenantId) {
CONTEXT.set(tenantId);
} public static String getTenant() { return CONTEXT.get();
} public static void clear() {
CONTEXT.remove();
}
}
4.2 自定义租户过滤器
java复制编辑@Componentpublic class TenantFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException { String tenantId = request.getHeader("X-Tenant-ID");
TenantContext.setTenant(tenantId); try {
chain.doFilter(request, response);
} finally {
TenantContext.clear();
}
}
}
五、MyBatis 实现租户数据隔离
5.1 添加租户字段
sql复制编辑CREATE TABLE product ( id BIGINT PRIMARY KEY, tenant_id VARCHAR(50), name VARCHAR(100), price DECIMAL);
5.2 SQL 加租户条件(拦截器)
java复制编辑@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})public class TenantInterceptor implements Interceptor { public Object intercept(Invocation invocation) throws Throwable { StatementHandler handler = (StatementHandler) PluginUtil.realTarget(invocation.getTarget()); String sql = handler.getBoundSql().getSql(); if (sql.toLowerCase().contains("where")) {
sql += " AND tenant_id = '" + TenantContext.getTenant() + "'";
} else {
sql += " WHERE tenant_id = '" + TenantContext.getTenant() + "'";
}
ReflectUtil.setFieldValue(handler.getBoundSql(), "sql", sql); return invocation.proceed();
}
}
六、Spring Data JPA 实现租户隔离
6.1 实体类定义
java复制编辑@Entitypublic class Product { @Id
private Long id; @Column
private String tenantId; private String name; private BigDecimal price;
}
6.2 自动填充租户字段
java复制编辑@Componentpublic class TenantEntityListener { @PrePersist
public void beforeSave(Object entity) { if (entity instanceof Product) {
((Product) entity).setTenantId(TenantContext.getTenant());
}
}
}
七、多租户下的 RBAC 权限控制设计
7.1 租户内权限模型(RBAC)
| 用户(user) | 角色(role) | 权限(perm) |
|---|---|---|
| tenant_id | tenant_id | tenant_id |
| user_id | role_id | perm_code |
每个租户拥有自己的用户、角色、权限模型。
7.2 查询权限示例
sql复制编辑SELECT perm_code FROM permWHERE tenant_id = ? AND user_id = ?
八、租户级配置与主题定制
8.1 配置项表结构
sql复制编辑CREATE TABLE tenant_config ( tenant_id VARCHAR(50), config_key VARCHAR(100), config_value TEXT );
8.2 动态读取配置
java复制编辑public String getTenantConfig(String key) { return configRepo.findByTenantIdAndKey(TenantContext.getTenant(), key);
}
九、多租户 + Gateway 实战组合
9.1 网关统一处理租户标识
在 Spring Cloud Gateway 的过滤器中提取 Header:
java复制编辑public class TenantGatewayFilter implements GlobalFilter { public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String tenantId = exchange.getRequest().getHeaders().getFirst("X-Tenant-ID");
exchange.getRequest().mutate().header("X-Tenant-ID", tenantId).build(); return chain.filter(exchange);
}
}
十、安全注意事项
| 风险 | 控制方式 |
|---|---|
| 数据越权访问 | 严格依赖租户 ID 过滤查询 |
| 接口暴露租户信息 | 不在响应中暴露租户 ID |
| Token 伪造或篡改 | Token 使用加密签名 + 有效期控制 |
| 高并发租户隔离不清晰 | 使用 ThreadLocal 正确清理上下文信息 |
十一、支持多租户的 SaaS 产品架构推荐
lua复制编辑 +----------------+ | 前端系统 | +--------+-------+ ↓ +--------------------------+ | API 网关(租户识别)| +--------------------------+ ↓ +-----------------+ +-----------------+ | 订单服务 | | 用户服务 | |(支持租户隔离) | |(租户 RBAC 模型) | +-----------------+ +-----------------+ +-------------------------+ | 多租户配置中心(DB/Nacos)| +-------------------------+
十二、总结与建议
通过本文,你已系统掌握多租户系统设计的全流程,包括:
-
多租户数据库模型对比与选型
-
请求中租户标识提取与传播机制
-
Spring Boot 实现租户上下文与 SQL 隔离
-
MyBatis/JPA 的多租户实践技巧
-
RBAC 权限与租户配置管理模型
-
多租户网关与安全控制建议
后续推荐实践:
-
租户动态切换数据源(AbstractRoutingDataSource)
-
多租户 SaaS 平台统一租户管理后台
-
支持租户上下线/冻结/续期机制
-
与 OAuth2 联动实现租户认证与授权分离
更多推荐


所有评论(0)