
做SaaS的同学应该都有体会:多租户这件事,看着简单,做着复杂。你以为是加个字段就能搞定,实际上牵一发而动全身——数据怎么存?资源怎么分?大租户吵着要独享,小租户只想省成本。
干了几年多租户系统,踩过不少坑,今天把几种常用方案掰开揉碎讲讲,顺便配上代码,希望能帮后来者少走弯路。
数据层的多租户设计,是所有方案的根基。2026年的今天,主流选择依然是三个梯队:共享表、共享库独立Schema、独立数据库。
这是所有SaaS产品的起点:一张orders表,里面有个tenant\_id字段,所有租户的订单混在一起。
实现起来最简单:查询时带上租户条件就行。
// MyBatis-Plus的多租户插件配置
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
@Override
public Expression getTenantId() {
// 从当前上下文获取租户ID
String tenantId = TenantContext.getCurrentTenant();
return new LongValue(Long.parseLong(tenantId));
}
@Override
public String getTenantIdColumn() {
return "tenant\_id";
}
}));
return interceptor;
}优点:省心省力,一个库搞定所有,资源利用率最高。跨租户统计一把梭,DBA最爱。
缺点:隔离全靠代码自觉。万一哪条SQL忘了加tenant\_id,数据就串了。更烦人的是“噪声邻居”——某个租户跑个复杂报表,CPU飙升,全库一起卡。
适合谁:几百个小型租户、业务模型高度统一的场景。比如一个给奶茶店用的进销存SaaS,每家店数据量不大,共享表完全够用。
这个模式我称之为“中产方案”:一个MySQL实例,但每个租户有自己的schema(数据库)。表结构可以按需调整——A租户要加个自定义字段,直接在它自己的schema里改,不影响B租户。
实现:动态切换数据源。
// 基于Spring的AbstractRoutingDataSource实现动态数据源
public class TenantRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return TenantContext.getCurrentTenant();
}
}
// 配置多个数据源(每个租户一个)
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.tenant1")
public DataSource tenant1DataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.tenant2")
public DataSource tenant2DataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DataSource routingDataSource() {
TenantRoutingDataSource routingDataSource = new TenantRoutingDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("tenant1", tenant1DataSource());
targetDataSources.put("tenant2", tenant2DataSource());
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDefaultTargetDataSource(tenant1DataSource());
return routingDataSource;
}
}代价:新增租户时,得自动创建schema并初始化表结构。租户上千之后,schema数量爆炸,备份和版本管理变得复杂。金仓数据库在新能源企业的实践就是这种模式:600多个风电场,每个独立schema,硬件成本降了45%,运维效率提了60%。
每个租户独享一个数据库实例,资源完全隔离,互不影响。备份恢复直接针对单个租户,满足金融、医疗等高合规要求。
实现:在应用层根据租户路由到不同数据库,可以用中间件如ShardingSphere,或者自己写路由逻辑。
// 使用Apache ShardingSphere的hint分片强制路由到指定库
HintManager hintManager = HintManager.getInstance();
hintManager.setDatabaseShardingValue(tenantId);
try {
// 执行SQL,会路由到tenantId对应的数据库
orderService.selectAll();
} finally {
hintManager.close();
}代价:数据库实例数随租户增长线性增加。1000个租户就是1000个库,连接管理、备份策略、版本升级全都要乘以N。Azure官方文档提醒:这种模式会大幅增加运维复杂性。而且如果每个租户写入量不大,会产生大量“小分片”,管理成本反而比共享模式更高。
适合谁:企业级大客户、对合规和数据隔离有极致要求的场景。
数据层隔离只是第一步,资源隔离同样重要。2026年,容器化和云原生技术让资源隔离变得更精细。
如果你的应用是Java写的,可以用Hystrix或Resilience4j给不同租户分配独立的线程池。这样即使某个租户的请求把线程池占满,也不会影响其他租户。
// Resilience4j的线程池隔离配置
@Bean
public ThreadPoolBulkhead tenantABulkhead() {
return ThreadPoolBulkhead.of("tenantA", ThreadPoolBulkheadConfig.custom()
.maxThreadPoolSize(10)
.coreThreadPoolSize(5)
.queueCapacity(20)
.build());
}
// 在业务代码中使用
@Bulkhead(name = "tenantA", type = Bulkhead.Type.THREADPOOL)
public CompletableFuture<Result> processTenantARequest(Request req) {
return CompletableFuture.supplyAsync(() -> {
// 处理租户A的业务
return service.process(req);
});
}在K8s层面,可以用Namespace做软隔离。每个租户一个Namespace,配合ResourceQuota限制CPU、内存使用量。
# 为租户A创建Namespace并设置资源配额
apiVersion: v1
kind: Namespace
metadata:
name: tenant-a
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-quota
namespace: tenant-a
spec:
hard:
requests.cpu: "4"
requests.memory: "8Gi"
limits.cpu: "8"
limits.memory: "16Gi"更进一步的隔离可以用虚拟集群(vCluster),在每个Namespace内再建虚拟控制面,隔离性更强,但复杂度也更高。
现实中没有完美的方案,只有动态的平衡。很多厂商选择“混合”路线:中小租户共享资源,大客户独享。
实现思路:在网关层根据租户等级动态路由。VIP租户走独立集群,普通租户走共享池。
// 伪代码:租户路由策略
public class TenantRouter {
@Autowired
private LoadBalancerClient loadBalancer;
public String route(String tenantId) {
TenantConfig config = getTenantConfig(tenantId);
if (config.getTier() == TenantTier.VIP) {
return "http://vip-service-" + tenantId; // 直接路由到独立服务
} else {
// 负载均衡到共享服务池
ServiceInstance instance = loadBalancer.choose("shared-service");
return instance.getUri().toString();
}
}
}数商云的做法也类似:采用“公有云底座+私有云定制”的混合模式,租户可以按业务敏感等级选择存储位置。合规要求高的走独立库,成本敏感的进共享池,统一网关层还能实现授权后的跨租户查询。
看完这些方案,可能有点眼花缭乱。其实选型时只要问清楚自己四个问题:
我的建议:从最简单的共享表起步,但设计之初就留好“后门”——tenant_id建好索引、表结构预留扩展字段、应用层做租户上下文传递。万一哪天业务发展,某个大客户要求独享资源,不至于重构整个系统。
Azure文档里有句话很实在:无论选哪种架构,都必须考虑“噪声邻居”的缓解措施——限流、缓存、存储控制,一个都不能少。
多租户不是一道“选A还是选B”的选择题,而是一道“愿意为隔离付出多少成本”的权衡题。
对于2026年的从业者来说,掌握这些方案的原理和实现细节,比背熟某个产品的操作手册重要得多。因为无论工具怎么变,隔离与效率的博弈永远不会变。
希望这篇文章里的代码和经验,能帮你多避开一个坑。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。