多租户实现方案

实现多租户的话,一般有以下几种实现方案,如下所示:

方案 描述 优点 缺点 使用场景
独立数据库 每个租户一个独立的DB实例 隔离性最好,物理隔离,单租户故障不影响其他人,备份回复容易 成本最高(连接池管理,迁移脚本执行),运维复杂,资源利用率低 对数据敏感的大客户
独立schema 同一个DB实例,每个租户一个Schema 隔离性较好,逻辑上分开,运维成本适中 跨租户统计困难,数据库连接数有上限,MySQL下Schema过多会影响性能 中型SaaS,对隔离有一定要求,且租户数量可控
共享表+租户id 所有租户共用一套表,通过tenant_id 字段区分 成本最低,资源利用率最高,开发维护简单,跨租户查询容易 隔离性最弱,代码层面一旦漏掉租户id,容易造成数据泄露 绝大多数saas平台,小微租户多,对成本敏感

我的决策:我会优选采用方案三(共享表+租户ID),因为性价比最高。

技术落地:

一、租户上下文的传递

在网关或者拦截器中,从Header中解析出tarentId,并使用ThreadLocal将TarentId放入当前线程的上下文中;

二、数据隔离的核心

利用Mybatis的拦截器,TenantInterceptor实现他的getTenantId方法

多线程情况下,上下文丢失,导致找不到tenantId;

可以利用spring的TaskDecorator,它允许我们在任务执行前后执行制定逻辑,我们可以通过配置,在任务执行前 设置tarentId,在任务执行后清理

bash 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

public class ExecutorConfig {

    // 假设这是你用于存储上下文的 ThreadLocal
    private static final ThreadLocal<String> MY_CONTEXT = new ThreadLocal<>();

    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(100);
        
        // 关键配置:设置 TaskDecorator
        executor.setTaskDecorator(new TaskDecorator() {
            @Override
            public Runnable decorate(Runnable runnable) {
                // 1. 在主线程中捕获上下文数据
                String contextValue = MY_CONTEXT.get();
                
                // 2. 返回一个包装后的 Runnable
                return () -> {
                    try {
                        // 3. 在子线程中设置捕获到的上下文
                        if (contextValue != null) {
                            MY_CONTEXT.set(contextValue);
                        }
                        // 4. 执行原始任务
                        runnable.run();
                    } finally {
                        // 5. 任务执行完毕,清理上下文,防止内存泄漏和数据污染
                        MY_CONTEXT.remove();
                    }
                };
            }
        });
        
        executor.initialize();
        return executor;
    }
}
相关推荐
2501_933923255 小时前
Spring Bean作用域揭秘:单例、原型、请求与会话的区别
java·后端·spring·java-ee
学编程就要猛5 小时前
基于Spring AI 的智能聊天机器人
java·spring ai·chat robot
修炼室5 小时前
Java开发工程师笔试经验贴【高频知识】
java
木井巳6 小时前
【记忆化搜索】最长递增子序列
java·算法·leetcode·深度优先·剪枝·推荐算法
杨杨杨大侠6 小时前
知识库已经有了,Java 程序员还要做什么?Spring AI RAG 实战
java·openai·ai编程
伊信6 小时前
Spring AI 基础学习与应用
java·后端
夜郎king6 小时前
基于 Java + Playwright 实现网站自动访问与数据采集(以腾讯云开发者社区为例)
java·playwright·网页数据获取
Anastasiozzzz6 小时前
重新定义 Agent 基建:Redis 在现代 AI 与智能体系统中的工程实践
java·人工智能·redis·ai
careathers6 小时前
【数据结构】队列
java·数据结构
步行cgn6 小时前
Spring 通过 factory-method 实例化 Bean 详解与常见错误排查
java·后端