Nd80a7TR0 发表于 2025-2-19 11:36:17

多租户的 4 种常用方案!

前言

某中型电商平台的报表系统曾在深夜突然崩溃,起因竟是运营误删了共享表中的某租户数据列。
运维团队排查发现,因为缺乏有效租户隔离,一条误操作的ALTER TABLE语句导致全平台数据混乱。
这让我们警惕:选择多租户方案的每一步,都是安全与成本的权衡。
今天这篇文章就跟大家一起聊聊,多租户的4种常用方案,希望对你会有所帮助。
一、字段隔离方案

低成本背后的高风险

字段隔离方案,是通过统一数据表+租户ID过滤实现逻辑隔离。
如下图所示:

初期开发成本极低,但将数据安全的压力完全转移到了代码质量控制上。
致命缺陷检查清单:

[*]任意一次DAO层查询漏加tenant_id条件 → 数据跨租户泄露
[*]索引必须将tenant_id作为最左前缀 → 性能瓶颈风险
[*]全表扫描类查询(如报表统计)无法避免跨租户干扰
代码防御示范

(1)MyBatis拦截器自动注入租户ID
@Intercepts({@Signature(type = Executor.class, method = "update")})public class TenantInterceptor implements Interceptor {      public Object intercept(Invocation iv) throws SQLException {          MappedStatement ms = (MappedStatement) iv.getArgs();          Object param = iv.getArgs();                  // 实体类自动填充tenant_id          if (param instanceof BaseTenantEntity) {            Field tenantIdField = param.getClass().getDeclaredField("tenantId");            tenantIdField.setAccessible(true);            if (tenantIdField.get(param) == null) {                  tenantIdField.set(param, TenantContext.get());            }          }          return iv.proceed();      }}(2)SQL防火墙:强制全表扫描必须声明租户范围
/* 危险操作(可能扫全表) */SELECT * FROM orders WHERE status = 'PAID';/* 安全写法(强制tenant_id过滤) */SELECT * FROM orders   WHERE tenant_id = 'tenant_01'    AND status = 'PAID'    /* 必须添加LIMIT防止全量拉取 */    LIMIT 1000;适用场景建议

[*]初期快速验证的MVP产品,用户量比较少的业务系统。
[*]对数据隔离要求较低的内部管理系统。
我最近开源了一个基于 SpringBoot+Vue+uniapp 的商城项目,里面的技术亮点挺多的,欢迎访问和star。[https://gitee.com/dvsusan/susan_mall]
二、Schema隔离

数据库层的单元房

在同一个数据库实例中为每个租户独立Schema,实现库级别隔离。
如下图所示:

各租户表结构相同但数据独立,像小区里的不同住户单元。
运维警告清单:

[*]百级Schema数量级后,备份与迁移成本陡增
[*]跨Schema关联查询必须引入中间聚合层
[*]数据库连接池需按最大租户数配置 → 连接风暴风险
动态路由代码实现

(1)Spring动态数据源配置
spring:    datasource:      dynamic:      primary: master      strict: true      datasource:          master:            url: jdbc:mysql://主库地址          tenant_001:            url: jdbc:mysql://从库地址?currentSchema=tenant_001          tenant_002:            url: jdbc:mysql://从库地址?currentSchema=tenant_002(2)AOP切面动态切换Schema
@Aspect@Componentpublic class SchemaAspect {      @Before("@annotation(requireTenant)")      public void switchSchema(JoinPoint joinPoint) {          HttpServletRequest request = getCurrentRequest();          String tenantId = request.getHeader("X-Tenant-ID");                  // 验证租户合法性          if (!tenantService.isValid(tenantId)) {            throw new IllegalTenantException("租户身份异常!");          }                  // 动态切换数据源          DynamicDataSourceContextHolder.push(tenantId);      }      @After("@annotation(requireTenant)")      public void clearSchema() {          DynamicDataSourceContextHolder.clear();      }}适用场景建议

[*]需要中等安全级别的行业(教育、零售)。
[*]租户数<50且数据规模可控的系统。
三、独立数据库

数据隔离的终极形态

每个租户享有独立数据库实例。
如下图所示:

从存储到底层连接完全隔离。
安全性最高但成本呈线性增长。
财务预警清单:

[*]每个实例约增加¥3000/月(云RDS基础配置)
[*]跨租户数据聚合需额外ETL系统支持
[*]DBA运维成本随租户数量直线上升
数据源动态路由核心代码

(1)抽象路由控制器
public class TenantDataSourceRouter extends AbstractRoutingDataSource {      @Override      protected Object determineCurrentLookupKey() {          return TenantContextHolder.get();      }      @Override      protected DataSource determineTargetDataSource() {          String tenantId = (String) determineCurrentLookupKey();          DataSource ds = dataSourceMap.get(tenantId);          if (ds == null) {            ds = createNewDataSource(tenantId);// 动态创建新租户数据源            dataSourceMap.put(tenantId, ds);          }          return ds;      }}(2)多租户事务同步器(关键!)
@Beanpublic PlatformTransactionManager transactionManager() {      return new DataSourceTransactionManager() {          @Override          protected void doBegin(Object transaction, TransactionDefinition definition) {            TenantDataSourceRouter router = (TenantDataSourceRouter) getDataSource();            router.initTenantDataSource(TenantContextHolder.get());// 确保事务绑定正确数据源            super.doBegin(transaction, definition);          }      };}适用场景建议

[*]金融、医疗等强合规行业
[*]付费能力强且需要独立资源池的KA客户
四、混合架构

没有银弹的平衡术

核心原则:按租户等级提供不同隔离方案
在系统中创建租户时,根据租户的实际情况,给它分配一个等级。
不同的等级,使用不同的隔离方案。
如下图所示:
租户等级隔离方案资源配置S级独立数据库独占RDS实例+只读副本A级Schema隔离共享实例独立SchemaB级字段过滤共享表动态策略选择器

针对不同的租户,我们可以使用策略模式,根据不同的等级,选择不同的数据库访问方式。
代码如下:
public class IsolationStrategyFactory {      public IsolationStrategy getStrategy(String tenantId) {          TenantConfig config = configService.getConfig(tenantId);          switch(config.getLevel()) {            case VIP:                  return new IndependentDBStrategy();            case STANDARD:                  return new SchemaStrategy();            case BASIC:            default:                  return new SharedTableStrategy();          }      }      // 示例策略接口      public interface IsolationStrategy {          DataSource getDataSource();          void executeQuery(String sql);      }}运维避坑必读

[*]元数据管理:建立租户-资源映射表,避免配置漂移
[*]迁移工具链:开发自动化升降级工具(如VIP客户从共享表迁移到独立库)
[*]监控分层:不同方案的性能指标需独立采集分析
总结

这篇文章列举了多租户的4种常用方案。
没有最完美的,只有最合适的。
多租户设计的本质是资源、安全、成本的黄金三角博弈。
与其追求理论完美,不如根据业务阶段选择最适方案。
毕竟能用可控成本解决问题的,才是真正的架构智慧。
如果看了文章有些收获,记得给我点赞喔,谢谢你的支持和鼓励。
最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。
页: [1]
查看完整版本: 多租户的 4 种常用方案!