Spring Boot 多租户架构实现:基于上下文自动传递的独立资源隔离方案
一、核心设计思想
通过线程上下文自动传递租户ID,结合动态数据源路由和中间件连接工厂,实现MySQL、Redis、RocketMQ的完全自动化资源隔离。关键设计如下:
二、关键实现方案
- 租户上下文管理(核心)
public class TenantContext {// 使用TransmittableThreadLocal支持异步传递private static final TransmittableThreadLocal<String> CONTEXT = new TransmittableThreadLocal<>();public static void set(String tenantId) {CONTEXT.set(tenantId);DynamicDataSourceContextHolder.push(tenantId); // 同步MySQL上下文}public static String get() {return CONTEXT.get();}public static void clear() {CONTEXT.remove();DynamicDataSourceContextHolder.poll(); // 清理MySQL上下文}
}
- MySQL动态数据源配置
@Configuration
@MapperScan(basePackages = "com.example.mapper")
public class DataSourceConfig {@Bean@ConfigurationProperties(prefix = "spring.datasource.master")public DataSource masterDataSource() {return DataSourceBuilder.create().build();}@Bean@ConfigurationProperties(prefix = "spring.datasource.tenantA")public DataSource tenantADatasource() {return DataSourceBuilder.create().build();}@Bean@ConfigurationProperties(prefix = "spring.datasource.tenantB")public DataSource tenantBDatasource() {return DataSourceBuilder.create().build();}@Beanpublic DataSource routingDataSource(@Qualifier("masterDataSource") DataSource master,@Qualifier("tenantADatasource") DataSource tenantA,@Qualifier("tenantBDatasource") DataSource tenantB) {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("tenantA", tenantA);targetDataSources.put("tenantB", tenantB);DynamicRoutingDataSource routingDataSource = new DynamicRoutingDataSource();routingDataSource.setDefaultTargetDataSource(master);routingDataSource.setTargetDataSources(targetDataSources);return routingDataSource;}
}public class DynamicRoutingDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceKey();}
}
- Redis连接工厂隔离
@Configuration
public class RedisConfig {@Beanpublic RedisConnectionFactory tenantARedisFactory() {RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();config.setDatabase(1); // 租户A专用DBreturn new LettuceConnectionFactory(config);}@Beanpublic RedisConnectionFactory tenantBRedisFactory() {RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();config.setDatabase(2); // 租户B专用DBreturn new LettuceConnectionFactory(config);}@Beanpublic RedisTemplate<String, Object> redisTemplate(@Qualifier("tenantARedisFactory") RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);template.setKeySerializer(new TenantAwareStringRedisSerializer());return template;}
}public class TenantAwareStringRedisSerializer implements StringRedisSerializer {@Overridepublic String serialize(String string) {String tenantId = TenantContext.get();return (tenantId == null) ? string : tenantId + ":" + string;}
}
- RocketMQ生产者/消费者自动绑定
@Configuration
public class RocketMQConfig {@Beanpublic DefaultMQProducer tenantProducer() throws MQClientException {DefaultMQProducer producer = new DefaultMQProducer("tenant_producer");producer.setNamesrvAddr("127.0.0.1:9876");producer.start();return producer;}@Beanpublic RocketMQListener<String> tenantListener() {return new TenantAwareListener();}
}public class TenantAwareListener implements RocketMQListener<String> {@Overridepublic void onMessage(String message) {String tenantId = TenantContext.get();// 自动路由到租户专属TopicString targetTopic = "topic_" + tenantId;// 处理消息...}
}
三、自动化上下文传播
- 请求拦截器
@Component
public class TenantInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String tenantId = request.getHeader("X-Tenant-ID");TenantContext.set(tenantId);return true;}@Overridepublic void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex) {TenantContext.clear();}
}
- 异步任务支持
@EnableAsync
@Configuration
public class AsyncConfig implements AsyncConfigurer {@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setTaskDecorator(new ContextCopyingDecorator());executor.initialize();return executor;}private static class ContextCopyingDecorator implements TaskDecorator {@Overridepublic Runnable decorate(Runnable runnable) {String tenantId = TenantContext.get();return () -> {try {TenantContext.set(tenantId);runnable.run();} finally {TenantContext.clear();}};}}
}
四、完整工作流程
-
请求入口
网关拦截请求 → 解析X-Tenant-ID
→ 设置到TenantContext
-
数据访问层
@Service public class OrderService {@Autowiredprivate OrderMapper orderMapper;public Order getOrder(Long id) {// 自动路由到当前租户的数据源return orderMapper.selectById(id);} }
-
Redis操作
@Service public class CacheService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;public void setValue(String key, Object value) {// 自动添加租户前缀redisTemplate.opsForValue().set(key, value);} }
-
消息发送
@Service public class MessageService {@Autowiredprivate DefaultMQProducer producer;public void sendOrderCreated(Order order) {Message msg = new Message("order_created", order.toJson());producer.send(msg); // 自动绑定租户Topic} }
五、技术优势对比
方案 | 上下文传递方式 | 中间件隔离级别 | 代码侵入性 | 线程安全 |
---|---|---|---|---|
手动切换 | 显式代码调用 | 应用层 | 高 | 需要处理 |
本方案 | 线程上下文自动传播 | 物理隔离 | 低 | 原生支持 |
动态注解 | AOP切面 | 逻辑隔离 | 中 | 需要配置 |
六、生产环境优化点
-
连接池监控
spring:datasource:tenantA:hikari:maximum-pool-size: 20leak-detection-threshold: 30000
-
RocketMQ消费者隔离
@RocketMQMessageListener(topic = "tenant_#{tenantId}_topic",consumerGroup = "tenant_#{tenantId}_group" ) public class TenantConsumer implements RocketMQListener<String> {// 自动注入当前租户的Consumer }
-
Redis集群支持
@Bean public RedisClusterConfiguration tenantARedisCluster() {RedisClusterConfiguration config = new RedisClusterConfiguration();config.setClusterNodes(Arrays.asList("127.0.0.1:7001", "127.0.0.1:7002"));config.setPassword("tenantA@2024");return config; }
通过本方案,可实现完全自动化的多租户资源隔离,核心优势在于:
- 零代码侵入:通过上下文自动传播实现资源隔离
- 物理级隔离:每个租户拥有独立数据库/消息队列/缓存集群
- 动态扩展:新增租户只需添加配置,无需修改代码