如何在Spring Boot 中实现多租户架构
目录
概述
1. 什么是多租户架构?
多租户架构是一种设计模式,允许单个应用程序服务于多个租户(Tenant)。在这种架构中,应用会为不同的租户提供隔离的数据和资源空间,确保租户间的相互独立。这类似于将应用“分割”为多个小实例,既共享底层代码,也能满足个性化的业务需求。
2. 多租户架构的优势
- 支持个性化需求:便于针对不同租户的业务差异进行快速定制。
- 降低运维成本:同一套代码支持多租户,大幅减少硬件、运维资源投入。
- 增强扩展性:支持横向扩展,可根据租户数自由调整资源分配。
3. 实现多租户架构的技术选择
选择适合的技术栈能加速多租户架构的实施。对于 Java 项目,Spring Boot 和 Spring Cloud 是常见选择。Spring Boot 提供快速的开发支持,而 Spring Cloud 的微服务组件(如服务发现、配置中心等)对多租户架构尤为重要。
设计思路
1. 架构选型
- Spring Boot:利用其自动配置特性,简化开发过程,提高效率。
- Spring Cloud:提供微服务化支持,实现服务的分布式部署与管理。
2. 数据库设计
多租户的数据库设计通常有两种方案:
- 单库多表:共享数据库,但每张表中增加
tenant_id
字段,以区分租户数据。 - 多库隔离:为每个租户建立独立数据库,数据完全隔离。
3. 应用多租户部署
实现多租户时应考虑两方面:
- 应用隔离:如使用 Docker、命名空间或虚拟机隔离各租户实例。
- 应用配置:为每个租户配置独立的参数(如端口、SSL 证书等),可以使用云配置中心或数据库存储。
4. 租户管理
需要实现租户信息维护和权限控制:
- 租户信息维护:包括增删查改操作,便于快速管理租户。
CREATE TABLE tenant (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE,
description VARCHAR(255),
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
- 租户权限控制:确保不同租户的数据互不可见,使用 Spring Security 实现访问权限管理。
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/tenant/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService())
.passwordEncoder(new BCryptPasswordEncoder())
.and()
.inMemoryAuthentication()
.withUser("admin")
.password(new BCryptPasswordEncoder().encode("123456"))
.roles("ADMIN");
}
}
技术实现
1. Spring Boot 中的多租户实现
1.1 多数据源实现
为每个租户配置单独的数据源,确保数据隔离。
@Configuration
public class DataSourceConfig {
@Bean(name = "dataSourceA")
@ConfigurationProperties(prefix = "spring.datasource.a")
public DataSource dataSourceA() {
return DataSourceBuilder.create().build();
}
@Bean(name = "dataSourceB")
@ConfigurationProperties(prefix = "spring.datasource.b")
public DataSource dataSourceB() {
return DataSourceBuilder.create().build();
}
@Bean(name = "dataSourceC")
@ConfigurationProperties(prefix = "spring.datasource.c")
public DataSource dataSourceC() {
return DataSourceBuilder.create().build();
}
}
以上代码是配置了三个数据源分别对应三个租户。然后在使用时,可以使用注解标记需要连接的数据源。@Service
public class ProductService {
@Autowired
@Qualifier("dataSourceA")
private DataSource dataSource;
// ...
}
1.2 动态路由实现
通过 AbstractRoutingDataSource
实现动态数据源切换:
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return TenantContextHolder.getTenantId();
}
}
@Configuration
public class DataSourceConfig {
@Bean(name = "dataSource")
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().type(DynamicDataSource.class).build();
}
}
以上是动态路由的核心代码DynamicDataSource继承自AbstractRoutingDataSource,通过determineCurrentLookupKey()方法动态获得租户ID,然后切换到对应的数据源。
2. Spring Cloud 中的多租户实现
通过 Spring Cloud 提供的微服务组件,实现多租户的集中管理:
- 服务注册与发现:使用 Eureka 等注册中心,为每个租户的服务实例注册不同的应用名称。
- 配置中心:使用 Spring Cloud Config,配置文件以租户 ID 区分。
- 负载均衡:使用 Ribbon 实现多租户的请求分发。
- API 网关:基于 URL 或参数实现租户路由。
应用场景
- 私有云环境:企业搭建的私有云,可以保证核心数据的安全性和可控性。
- 公有云环境:多租户 SaaS 平台便于快速扩展和灵活分配资源。
- 企业级应用:适用于 ERP、CRM 等大型企业级系统。
实现步骤
1. 搭建 Spring Boot 和 Spring Cloud 环境
在 pom.xml
引入相关依赖,并在 application.yml
中配置数据源和 Eureka 注册中心地址。
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2020.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
然后需要在application.yml中配置相应的参数,如下所示:
spring:
datasource:
url: jdbc:mysql://localhost:3306/appdb?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
mybatis:
type-aliases-package: com.example.demo.model
mapper-locations: classpath:mapper/*.xml
server:
port: 8080
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
management:
endpoints:
web:
exposure:
include: "*"
其中datasource.url为数据库连接的URL,username和password为数据库连接的账号和密码;server.port为Spring Boot应用启动的端口;eureka.client.serviceUrl.defaultZone为Eureka服务注册中心的URL。
2. 修改数据库设计
添加 tenant_id
字段用于标识租户,或根据需求配置多个独立数据库。
3. 实现应用多租户部署
在应用配置中创建一个多租户配置类,动态选择租户的数据源和 Session 工厂:
@Configuration
public class MultiTenantConfig {
// 提供对应租户的数据源
@Bean
public DataSource dataSource(TenantRegistry tenantRegistry) {
return new TenantAwareDataSource(tenantRegistry);
}
// 多租户Session工厂
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DataSource dataSource)
throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
return sessionFactory.getObject();
}
// 动态切换租户
@Bean
public MultiTenantInterceptor multiTenantInterceptor(TenantResolver tenantResolver) {
MultiTenantInterceptor interceptor = new MultiTenantInterceptor();
interceptor.setTenantResolver(tenantResolver);
return interceptor;
}
// 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(multiTenantInterceptor());
}
// 注册租户信息
@Bean
public TenantRegistry tenantRegistry() {
return new TenantRegistryImpl();
}
// 解析租户ID
@Bean
public TenantResolver tenantResolver() {
return new HeaderTenantResolver();
}
}
4. 实现租户管理
最后需要实现一个租户管理的功能,以便在系统中管理不同的租户。具体来说,我们可以使用Spring Cloud的服务注册与发现组件Eureka来注册每个租户的实例,并在管理界面中进行相应的操作。另外,我们还需要为每个租户提供一个独立的数据库,以保证数据隔离性。
总结与回顾
通过 Spring Boot 和 Spring Cloud,可以实现一个灵活、可扩展的多租户应用。适用于多租户 SaaS 平台,简化运维、增强扩展性。未来可进一步自动化租户管理,优化系统稳定性与易维护性。
评论区