某中型电商平台的报表系统曾在深夜突然崩溃,起因竟是运营误删了共享表中的某租户数据列。
运维团队排查发现,因为缺乏有效租户隔离,一条误操作的ALTER TABLE语句导致全平台数据混乱。
这让我们警惕:选择多租户方案的每一步,都是安全与成本的权衡。
今天这篇文章就跟大家一起聊聊,多租户的4种常用方案,希望对你会有所帮助。
字段隔离方案,是通过统一数据表+租户ID过滤实现逻辑隔离。
如下图所示:
图片
初期开发成本极低,但将数据安全的压力完全转移到了代码质量控制上。
致命缺陷检查清单:
任意一次DAO层查询漏加tenant_id条件 → 数据跨租户泄露
索引必须将tenant_id作为最左前缀 → 性能瓶颈风险
全表扫描类查询(如报表统计)无法避免跨租户干扰
(1)MyBatis拦截器自动注入租户ID
@Intercepts({@Signature(type=Executor.class,method="update")})publicclass TenantInterceptor implements Interceptor {publicObject intercept(Invocation iv)throws SQLException { MappedStatement ms=(MappedStatement)iv.getArgs()[0];Object param=iv.getArgs()[1];// 实体类自动填充tenant_idif(param instanceof BaseTenantEntity){ Field tenantIdField=param.getClass().getDeclaredField("tenantId");tenantIdField.setAccessible(true);if(tenantIdField.get(param)==null){ tenantIdField.set(param,TenantContext.get());} }returniv.proceed();} }
(2)SQL防火墙:强制全表扫描必须声明租户范围
/* 危险操作(可能扫全表) */SELECT*FROMordersWHEREstatus='PAID';/* 安全写法(强制tenant_id过滤) */SELECT*FROMordersWHEREtenant_id='tenant_01'ANDstatus='PAID'/* 必须添加LIMIT防止全量拉取 */LIMIT1000;
适用场景建议
初期快速验证的MVP产品,用户量比较少的业务系统。
对数据隔离要求较低的内部管理系统。
在同一个数据库实例中为每个租户独立Schema,实现库级别隔离。
如下图所示:
图片
各租户表结构相同但数据独立,像小区里的不同住户单元。
运维警告清单:
百级Schema数量级后,备份与迁移成本陡增
跨Schema关联查询必须引入中间聚合层
数据库连接池需按最大租户数配置 → 连接风暴风险
(1)Spring动态数据源配置
spring: datasource: dynamic:primary: master strict:truedatasource: master: url: jdbc:mysql://主库地址tenant_001: url: jdbc:mysql://从库地址?currentSchema=tenant_001tenant_002: url: jdbc:mysql://从库地址?currentSchema=tenant_002
(2)AOP切面动态切换Schema
@Aspect@Componentpublicclass SchemaAspect {@Before("@annotation(requireTenant)")publicvoid 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)")publicvoid clearSchema(){ DynamicDataSourceContextHolder.clear();} }
适用场景建议
需要中等安全级别的行业(教育、零售)。
租户数<50且数据规模可控的系统。
每个租户享有独立数据库实例。
如下图所示:
图片
从存储到底层连接完全隔离。
安全性最高但成本呈线性增长。
财务预警清单:
每个实例约增加¥3000/月(云RDS基础配置)
跨租户数据聚合需额外ETL系统支持
DBA运维成本随租户数量直线上升
(1)抽象路由控制器
publicclass TenantDataSourceRouter extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey(){returnTenantContextHolder.get();}@Overrideprotected DataSource determineTargetDataSource(){ String tenantId=(String)determineCurrentLookupKey();DataSource ds=dataSourceMap.get(tenantId);if(ds==null){ ds=createNewDataSource(tenantId);// 动态创建新租户数据源dataSourceMap.put(tenantId,ds);}returnds;} }
(2)多租户事务同步器(关键!)
@BeanpublicPlatformTransactionManager transactionManager(){returnnew DataSourceTransactionManager(){@Overrideprotected void doBegin(Objecttransaction,TransactionDefinition definition){ TenantDataSourceRouter router=(TenantDataSourceRouter)getDataSource();router.initTenantDataSource(TenantContextHolder.get());// 确保事务绑定正确数据源super.doBegin(transaction,definition);} };}
适用场景建议
金融、医疗等强合规行业
付费能力强且需要独立资源池的KA客户
核心原则:按租户等级提供不同隔离方案
在系统中创建租户时,根据租户的实际情况,给它分配一个等级。
不同的等级,使用不同的隔离方案。
如下图所示:
租户等级 | 隔离方案 | 资源配置 |
S级 | 独立数据库 | 独占RDS实例+只读副本 |
A级 | Schema隔离 | 共享实例独立Schema |
B级 | 字段过滤 | 共享表 |
针对不同的租户,我们可以使用策略模式,根据不同的等级,选择不同的数据库访问方式。
代码如下:
publicclass IsolationStrategyFactory {publicIsolationStrategy getStrategy(String tenantId){ TenantConfig config=configService.getConfig(tenantId);switch(config.getLevel()){caseVIP:returnnew IndependentDBStrategy();caseSTANDARD:returnnew SchemaStrategy();caseBASIC:default:returnnew SharedTableStrategy();} }// 示例策略接口publicinterface IsolationStrategy { DataSource getDataSource();void executeQuery(Stringsql);} }
运维避坑必读
元数据管理:建立租户-资源映射表,避免配置漂移
迁移工具链:开发自动化升降级工具(如VIP客户从共享表迁移到独立库)
监控分层:不同方案的性能指标需独立采集分析
这篇文章列举了多租户的4种常用方案。
没有最完美的,只有最合适的。
多租户设计的本质是资源、安全、成本的黄金三角博弈。
与其追求理论完美,不如根据业务阶段选择最适方案。
毕竟能用可控成本解决问题的,才是真正的架构智慧。
如果看了文章有些收获,记得给我点赞喔,谢谢你的支持和鼓励。