经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 编程经验 » 查看文章
多租户的 4 种常用方案!
来源:cnblogs  作者:苏三说技术  时间:2025/2/20 10:42:45  对本文有异议

前言

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

运维团队排查发现,因为缺乏有效租户隔离,一条误操作的ALTER TABLE语句导致全平台数据混乱。

这让我们警惕:选择多租户方案的每一步,都是安全与成本的权衡

今天这篇文章就跟大家一起聊聊,多租户的4种常用方案,希望对你会有所帮助。

一、字段隔离方案

低成本背后的高风险

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

如下图所示:

初期开发成本极低,但将数据安全的压力完全转移到了代码质量控制上。

致命缺陷检查清单

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

代码防御示范

(1)MyBatis拦截器自动注入租户ID

  1. @Intercepts({@Signature(type = Executor.class, method = "update")})
  2. public class TenantInterceptor implements Interceptor {
  3. public Object intercept(Invocation iv) throws SQLException {
  4. MappedStatement ms = (MappedStatement) iv.getArgs()[0];
  5. Object param = iv.getArgs()[1];
  6. // 实体类自动填充tenant_id
  7. if (param instanceof BaseTenantEntity) {
  8. Field tenantIdField = param.getClass().getDeclaredField("tenantId");
  9. tenantIdField.setAccessible(true);
  10. if (tenantIdField.get(param) == null) {
  11. tenantIdField.set(param, TenantContext.get());
  12. }
  13. }
  14. return iv.proceed();
  15. }
  16. }

(2)SQL防火墙:强制全表扫描必须声明租户范围

  1. /* 危险操作(可能扫全表) */
  2. SELECT * FROM orders WHERE status = 'PAID';
  3. /* 安全写法(强制tenant_id过滤) */
  4. SELECT * FROM orders
  5. WHERE tenant_id = 'tenant_01'
  6. AND status = 'PAID'
  7. /* 必须添加LIMIT防止全量拉取 */
  8. LIMIT 1000;

适用场景建议

  • 初期快速验证的MVP产品,用户量比较少的业务系统。
  • 对数据隔离要求较低的内部管理系统。

我最近开源了一个基于 SpringBoot+Vue+uniapp 的商城项目,里面的技术亮点挺多的,欢迎访问和star。[https://gitee.com/dvsusan/susan_mall]

二、Schema隔离

数据库层的单元房

在同一个数据库实例中为每个租户独立Schema,实现库级别隔离

如下图所示:

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

运维警告清单

  • 百级Schema数量级后,备份与迁移成本陡增
  • 跨Schema关联查询必须引入中间聚合层
  • 数据库连接池需按最大租户数配置 → 连接风暴风险

动态路由代码实现

(1)Spring动态数据源配置

  1. spring:
  2. datasource:
  3. dynamic:
  4. primary: master
  5. strict: true
  6. datasource:
  7. master:
  8. url: jdbc:mysql://主库地址
  9. tenant_001:
  10. url: jdbc:mysql://从库地址?currentSchema=tenant_001
  11. tenant_002:
  12. url: jdbc:mysql://从库地址?currentSchema=tenant_002

(2)AOP切面动态切换Schema

  1. @Aspect
  2. @Component
  3. public class SchemaAspect {
  4. @Before("@annotation(requireTenant)")
  5. public void switchSchema(JoinPoint joinPoint) {
  6. HttpServletRequest request = getCurrentRequest();
  7. String tenantId = request.getHeader("X-Tenant-ID");
  8. // 验证租户合法性
  9. if (!tenantService.isValid(tenantId)) {
  10. throw new IllegalTenantException("租户身份异常!");
  11. }
  12. // 动态切换数据源
  13. DynamicDataSourceContextHolder.push(tenantId);
  14. }
  15. @After("@annotation(requireTenant)")
  16. public void clearSchema() {
  17. DynamicDataSourceContextHolder.clear();
  18. }
  19. }

适用场景建议

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

三、独立数据库

数据隔离的终极形态

每个租户享有独立数据库实例

如下图所示:

从存储到底层连接完全隔离。

安全性最高但成本呈线性增长。

财务预警清单

  • 每个实例约增加¥3000/月(云RDS基础配置)
  • 跨租户数据聚合需额外ETL系统支持
  • DBA运维成本随租户数量直线上升

数据源动态路由核心代码

(1)抽象路由控制器

  1. public class TenantDataSourceRouter extends AbstractRoutingDataSource {
  2. @Override
  3. protected Object determineCurrentLookupKey() {
  4. return TenantContextHolder.get();
  5. }
  6. @Override
  7. protected DataSource determineTargetDataSource() {
  8. String tenantId = (String) determineCurrentLookupKey();
  9. DataSource ds = dataSourceMap.get(tenantId);
  10. if (ds == null) {
  11. ds = createNewDataSource(tenantId); // 动态创建新租户数据源
  12. dataSourceMap.put(tenantId, ds);
  13. }
  14. return ds;
  15. }
  16. }

(2)多租户事务同步器(关键!)

  1. @Bean
  2. public PlatformTransactionManager transactionManager() {
  3. return new DataSourceTransactionManager() {
  4. @Override
  5. protected void doBegin(Object transaction, TransactionDefinition definition) {
  6. TenantDataSourceRouter router = (TenantDataSourceRouter) getDataSource();
  7. router.initTenantDataSource(TenantContextHolder.get()); // 确保事务绑定正确数据源
  8. super.doBegin(transaction, definition);
  9. }
  10. };
  11. }

适用场景建议

  • 金融、医疗等强合规行业
  • 付费能力强且需要独立资源池的KA客户

四、混合架构

没有银弹的平衡术

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

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

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

如下图所示:

租户等级 隔离方案 资源配置
S级 独立数据库 独占RDS实例+只读副本
A级 Schema隔离 共享实例独立Schema
B级 字段过滤 共享表

动态策略选择器

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

代码如下:

  1. public class IsolationStrategyFactory {
  2. public IsolationStrategy getStrategy(String tenantId) {
  3. TenantConfig config = configService.getConfig(tenantId);
  4. switch(config.getLevel()) {
  5. case VIP:
  6. return new IndependentDBStrategy();
  7. case STANDARD:
  8. return new SchemaStrategy();
  9. case BASIC:
  10. default:
  11. return new SharedTableStrategy();
  12. }
  13. }
  14. // 示例策略接口
  15. public interface IsolationStrategy {
  16. DataSource getDataSource();
  17. void executeQuery(String sql);
  18. }
  19. }

运维避坑必读

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

总结

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

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

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

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

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

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

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。

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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号