写点什么

多租户的 4 种常用方案!

  • 2025-02-21
    福建
  • 本文字数:3070 字

    阅读完需:约 10 分钟

前言


某中型电商平台的报表系统曾在深夜突然崩溃,起因竟是运营误删了共享表中的某租户数据列。


运维团队排查发现,因为缺乏有效租户隔离,一条误操作的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()[0];          Object param = iv.getArgs()[1];                  // 实体类自动填充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 产品,用户量比较少的业务系统。

  • 对数据隔离要求较低的内部管理系统。


二、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  @Component  public 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)多租户事务同步器(关键!)


@Bean  public 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 客户


四、混合架构


没有银弹的平衡术


核心原则:按租户等级提供不同隔离方案


在系统中创建租户时,根据租户的实际情况,给它分配一个等级。


不同的等级,使用不同的隔离方案。


如下图所示:



动态策略选择器


针对不同的租户,我们可以使用策略模式,根据不同的等级,选择不同的数据库访问方式。


代码如下:


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); } }
复制代码


运维避坑必读

  1. 元数据管理:建立租户-资源映射表,避免配置漂移

  2. 迁移工具链:开发自动化升降级工具(如 VIP 客户从共享表迁移到独立库)

  3. 监控分层:不同方案的性能指标需独立采集分析


总结


这篇文章列举了多租户的 4 种常用方案。


没有最完美的,只有最合适的。


多租户设计的本质是资源、安全、成本的黄金三角博弈


与其追求理论完美,不如根据业务阶段选择最适方案。


毕竟能用可控成本解决问题的,才是真正的架构智慧。


如果看了文章有些收获,记得给我点赞喔,谢谢你的支持和鼓励。


文章转载自:苏三说技术

原文链接:https://www.cnblogs.com/12lisu/p/18722459

体验地址:http://www.jnpfsoft.com/?from=001YH

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
多租户的 4 种常用方案!_大数据_快乐非自愿限量之名_InfoQ写作社区