首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >多租户的几种常用方案:从代码到架构的隔离

多租户的几种常用方案:从代码到架构的隔离

原创
作者头像
李白客
发布2026-03-12 11:01:18
发布2026-03-12 11:01:18
250
举报

做SaaS的同学应该都有体会:多租户这件事,看着简单,做着复杂。你以为是加个字段就能搞定,实际上牵一发而动全身——数据怎么存?资源怎么分?大租户吵着要独享,小租户只想省成本。

干了几年多租户系统,踩过不少坑,今天把几种常用方案掰开揉碎讲讲,顺便配上代码,希望能帮后来者少走弯路。

一、数据层的隔离:三种经典模式

数据层的多租户设计,是所有方案的根基。2026年的今天,主流选择依然是三个梯队:共享表、共享库独立Schema、独立数据库。

1. 共享表:最简单的起步

这是所有SaaS产品的起点:一张orders表,里面有个tenant\_id字段,所有租户的订单混在一起。

实现起来最简单:查询时带上租户条件就行。

代码语言:java
复制
// 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,每家店数据量不大,共享表完全够用。

2. 共享数据库,独立Schema:隔离与成本的折中

这个模式我称之为“中产方案”:一个MySQL实例,但每个租户有自己的schema(数据库)。表结构可以按需调整——A租户要加个自定义字段,直接在它自己的schema里改,不影响B租户。

实现:动态切换数据源。

代码语言:java
复制
// 基于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%。

3. 独立数据库:顶配隔离

每个租户独享一个数据库实例,资源完全隔离,互不影响。备份恢复直接针对单个租户,满足金融、医疗等高合规要求。

实现:在应用层根据租户路由到不同数据库,可以用中间件如ShardingSphere,或者自己写路由逻辑。

代码语言:java
复制
// 使用Apache ShardingSphere的hint分片强制路由到指定库

HintManager hintManager = HintManager.getInstance();

hintManager.setDatabaseShardingValue(tenantId);

try {

    // 执行SQL,会路由到tenantId对应的数据库

    orderService.selectAll();

} finally {

    hintManager.close();

}

代价:数据库实例数随租户增长线性增加。1000个租户就是1000个库,连接管理、备份策略、版本升级全都要乘以N。Azure官方文档提醒:这种模式会大幅增加运维复杂性。而且如果每个租户写入量不大,会产生大量“小分片”,管理成本反而比共享模式更高。

适合谁:企业级大客户、对合规和数据隔离有极致要求的场景。

二、资源隔离:别让大租户拖垮所有人

数据层隔离只是第一步,资源隔离同样重要。2026年,容器化和云原生技术让资源隔离变得更精细。

1. 线程池隔离

如果你的应用是Java写的,可以用Hystrix或Resilience4j给不同租户分配独立的线程池。这样即使某个租户的请求把线程池占满,也不会影响其他租户。

代码语言:java
复制
// 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);

    });

}

2. Kubernetes命名空间隔离

在K8s层面,可以用Namespace做软隔离。每个租户一个Namespace,配合ResourceQuota限制CPU、内存使用量。

代码语言:yaml
复制
# 为租户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租户走独立集群,普通租户走共享池。

代码语言:java
复制
// 伪代码:租户路由策略

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();

        }

    }

}

数商云的做法也类似:采用“公有云底座+私有云定制”的混合模式,租户可以按业务敏感等级选择存储位置。合规要求高的走独立库,成本敏感的进共享池,统一网关层还能实现授权后的跨租户查询。

四、选型建议:先问自己四个问题

看完这些方案,可能有点眼花缭乱。其实选型时只要问清楚自己四个问题:

  1. 数据有多敏感? 受不受GDPR、等保之类的要求?
  2. 经常需要跨租户统计吗? 比如老板要算所有租户的总销售额。
  3. 租户之间业务差异大不大? 需不需要各自定制表结构?
  4. 团队能运维多复杂的架构?是几个人小团队,还是几十人专业运维?

我的建议:从最简单的共享表起步,但设计之初就留好“后门”——tenant_id建好索引、表结构预留扩展字段、应用层做租户上下文传递。万一哪天业务发展,某个大客户要求独享资源,不至于重构整个系统。

Azure文档里有句话很实在:无论选哪种架构,都必须考虑“噪声邻居”的缓解措施——限流、缓存、存储控制,一个都不能少。

结语

多租户不是一道“选A还是选B”的选择题,而是一道“愿意为隔离付出多少成本”的权衡题。

对于2026年的从业者来说,掌握这些方案的原理和实现细节,比背熟某个产品的操作手册重要得多。因为无论工具怎么变,隔离与效率的博弈永远不会变。

希望这篇文章里的代码和经验,能帮你多避开一个坑。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、数据层的隔离:三种经典模式
    • 1. 共享表:最简单的起步
    • 2. 共享数据库,独立Schema:隔离与成本的折中
    • 3. 独立数据库:顶配隔离
  • 二、资源隔离:别让大租户拖垮所有人
    • 1. 线程池隔离
    • 2. Kubernetes命名空间隔离
  • 三、混合架构:动态平衡的艺术
  • 四、选型建议:先问自己四个问题
  • 结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档