第一次接触SaaS多租户架构时,我正负责一个企业级CRM系统的重构。客户要求系统能同时支持上百家企业使用,但运维团队只有3个人。传统单租户架构意味着要部署上百套环境,这显然不现实。正是这次经历让我深刻认识到多租户架构的价值。
多租户架构的核心在于"共享与隔离"的平衡。想象一栋写字楼:所有公司共享电梯、水电等基础设施(共享资源),但每个公司有独立的办公室和门禁系统(数据隔离)。这种模式在软件领域同样适用,具体表现为三个关键特征:
在实际项目中,这种架构带来的收益非常明显。以我参与的电商SaaS平台为例:
这种模式就像合租公寓,所有租户共用同一个房间(数据库)和储物柜(Schema),通过贴标签(tenant_id)区分物品。我在一个轻量级OA系统中采用过这种方案:
java复制// 典型的数据表结构设计
CREATE TABLE document (
id BIGINT PRIMARY KEY,
tenant_id VARCHAR(32) NOT NULL, -- 关键字段
title VARCHAR(255),
content TEXT,
INDEX idx_tenant (tenant_id) -- 必须建立索引
);
实战经验:
这相当于写字楼里的独立办公室。我们为每个租户创建专属Schema:
sql复制-- 租户注册时自动执行
CREATE SCHEMA tenant_001;
CREATE TABLE tenant_001.document (...);
在Spring Boot中实现Schema切换的关键代码:
java复制public class TenantSchemaResolver {
public static String resolveSchema(HttpServletRequest request) {
String tenantId = request.getHeader("X-Tenant-ID");
return "tenant_" + tenantId; // 动态Schema命名规则
}
}
// 在JPA配置中动态设置Schema
@Configuration
public class JpaConfig {
@Bean
public HibernatePropertiesCustomizer hibernatePropertiesCustomizer() {
return props -> props.put(
"hibernate.default_schema",
TenantContext.getCurrentSchema()
);
}
}
踩坑记录:
这种模式就像给每个租户一栋独立别墅。在金融级SaaS中我们这样实现:
yaml复制# application-tenant001.yml
spring:
datasource:
url: jdbc:mysql://cluster1/tenant001
username: tenant001_user
password: xxxx
# 动态数据源配置
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSourceProperties dataSourceProperties() {
return new DataSourceProperties();
}
@Bean
public RoutingDataSource routingDataSource() {
Map<Object, DataSource> dataSources = new HashMap<>();
// 从配置中心加载所有租户数据源
loadTenantDataSources().forEach((k,v) ->
dataSources.put(k, v.initializeDataSourceBuilder().build()));
// ...
}
关键考量:
上下文传递就像快递面单,必须全程跟随请求链路。我们设计的增强版上下文方案:
java复制public class TenantContext {
private static final ThreadLocal<Tenant> CONTEXT = new ThreadLocal<>();
public static void setTenant(Tenant tenant) {
CONTEXT.set(tenant);
}
public static Tenant getTenant() {
return CONTEXT.get();
}
// 支持异步上下文传递
public static Runnable wrap(Runnable task) {
Tenant tenant = getTenant();
return () -> {
try {
setTenant(tenant);
task.run();
} finally {
clear();
}
};
}
}
// Feign客户端拦截器
public class TenantFeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
template.header("X-Tenant-ID", TenantContext.getTenant().getId());
}
}
注意事项:
我们开发了智能化的SQL拦截器,避免开发人员遗漏tenant_id:
java复制public class TenantSqlInterceptor implements Interceptor {
private static final Set<String> IGNORE_TABLES = Set.of(
"tenant", "system_config"
);
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 解析SQL和参数
// ...
if (!IGNORE_TABLES.contains(tableName)
&& !sqlContainsTenantCondition(parsedSql)) {
String newSql = addTenantCondition(parsedSql);
resetSql(metaObject, newSql);
}
return invocation.proceed();
}
private String addTenantCondition(String sql) {
// 智能识别WHERE/JOIN等位置插入条件
// 例如: SELECT * FROM doc -> SELECT * FROM doc WHERE tenant_id = ?
}
}
我们在RBAC模型基础上增加了租户维度:
java复制@Entity
public class Role {
@Id
private Long id;
@Column(name = "tenant_id")
private String tenantId; // 租户隔离
private String code;
private String name;
@ManyToMany
@JoinTable(
name = "role_permission",
joinColumns = {
@JoinColumn(name = "role_id"),
@JoinColumn(name = "tenant_id") // 联合主键
},
inverseJoinColumns = @JoinColumn(name = "permission_id")
)
private Set<Permission> permissions;
}
最佳实践:
我们最终采用的混合方案:
java复制public class TenantConfigService {
@Cacheable(cacheNames = "tenant_config", key = "#tenantId + ':' + #key")
public String getConfig(String tenantId, String key) {
// 1. 先查本地数据库配置表
String value = jdbcTemplate.queryForObject(
"SELECT config_value FROM tenant_config WHERE tenant_id = ? AND config_key = ?",
String.class, tenantId, key);
if (value == null) {
// 2. 查配置中心
value = nacosConfigService.getConfig(
"tenant." + tenantId + "." + key,
"DEFAULT_GROUP", 3000);
}
return value;
}
}
我们在电商SaaS中遇到的典型问题及解决方案:
问题场景:大促期间某个租户的复杂报表查询拖慢整个数据库
解决方案:
sql复制-- MySQL 8.0资源组
CREATE RESOURCE GROUP big_tenant
TYPE = USER
VCPU = 2-3
THREAD_PRIORITY = 8;
ALTER USER 'big_tenant_user' RESOURCE GROUP big_tenant;
我们开发的自动化流程:
注册阶段:
扩容阶段:
注销阶段:
最近我们在探索的两种创新方案:
方案一:Kubernetes多租户集群
方案二:Serverless架构
经过多个项目的实践验证,我认为多租户架构的成功关键在于:在设计初期就明确隔离级别需求,建立完善的租户上下文管理体系,并为可能的架构演进预留扩展点。