随着 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 联动实现租户认证与授权分离

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐