什么是多租户架构?
# 一、概述
# 1. 什么是多租户架构?
多租户架构是指在一个应用中支持多个租户(Tenant)同时访问,每个租户拥有独立的资源和数据,并且彼此之间完全隔离。
通俗来说,多租户就是把一个应用按照客户的需求 “分割” 成多个独立的实例,每个实例互不干扰。
# 2. 多租户架构的优势
更好地满足不同租户的个性化需求。
可以降低运维成本,减少硬件、网络等基础设施的投入。
节约开发成本,通过复用代码,快速上线新的租户实例。
增强了系统的可扩展性和可伸缩性,支持水平扩展,每个租户的数据和资源均可管理和控制。
# 3. 实现多租户架构的技术选择
对于实现多租户架构技术不是最重要的最重要的是正确的架构思路。但是选择正确的技术可以更快地实现多租户架构。
# 二、设计思路
# 1. 架构选型
基于 Java 开发多租户应用推荐使用 Spring Boot 和 Spring Cloud。Spring Boot 能快速搭建应用并提供许多成熟的插件。Spring Cloud 则提供了许多实现微服务架构的工具和组件。
# 1.1 Spring Boot
使用 Spring Boot 可以简化项目的搭建过程自动配置许多常见的第三方库和组件,减少了开发人员的工作量。
@RestController
public class TenantController {
@GetMapping("/hello")
public String hello(@RequestHeader("tenant-id") String tenantId) {
return "Hello, " + tenantId;
}
}
2
3
4
5
6
7
8
# 1.2 Spring Cloud
在架构多租户的系统时 Spring Cloud 会更加有用。Spring Cloud 提供了一些成熟的解决方案,如 Eureka、Zookeeper、Consul 等,以实现服务发现、负载均衡等微服务功能。
另外,如果你近期准备面试跳槽,建议在 Java 面试库小程序在线刷题,涵盖 2000+ 道 Java 面试题,几乎覆盖了所有主流技术面试题。
# 2. 数据库设计
在多租户环境中数据库必须为每个租户分别存储数据并确保数据隔离。我们通常使用以下两种方式实现:
多个租户共享相同的数据库,每个表中都包含 tenant_id 这一列,用于区分不同租户的数据。
为每个租户创建单独的数据库,每个数据库内的表结构相同,但数据相互隔离。
# 3. 应用多租户部署
为了实现多租户在应用部署时我们需要考虑以下两个问题。
# 3.1 应用隔离
在多租户环境中不同租户需要访问不同的资源,因此需要进行应用隔离。可以通过构建独立的容器或虚拟机、使用命名空间等方式实现。Docker 就是一种非常流行的隔离容器技术。
# 3.2 应用配置
由于每个租户都有自己的配置需求因此需要为每个租户分别设置应用配置信息,例如端口号、SSL 证书等等。这些配置可以存储在数据库中,也可以存储在云配置中心中。
# 4. 租户管理
在多租户系统中需要能够管理不同租户的数据和资源,同时需要为每个租户分配相应的权限。解决方案通常包括以下两部分。
# 4.1 租户信息维护
租户信息的维护包括添加、修改、删除、查询等操作,要求能够根据租户名称或租户 ID 快速查找对应的租户信息。
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
);
2
3
4
5
6
7
# 4.2 租户权限控制
在多租户应用中必须为每个租户分别设置对系统资源的访问权限。例如,A 租户和 B 租户不能访问彼此的数据。
@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");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 三、技术实现
# 1. Spring Boot 中的多租户实现
在 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();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
以上代码是配置了三个数据源分别对应三个租户。然后在使用时,可以使用注解标记需要连接的数据源。
@Service
public class ProductService {
@Autowired
@Qualifier("dataSourceA")
private DataSource dataSource;
// ...
}
2
3
4
5
6
7
8
9
10
# 1.2 动态路由实现
动态路由是指根据请求的 URL 或参数动态地切换到对应租户的数据源。具体实现如下:
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();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
以上是动态路由的核心代码DynamicDataSource
继承自AbstractRoutingDataSource
,通过determineCurrentLookupKey()
方法动态获得租户 ID,然后切换到对应的数据源。
# 2. Spring Cloud 中的多租户实现
在 Spring Cloud 中可以通过服务注册与发现、配置中心、负载均衡等方式实现多租户机制。
# 2.1 服务注册与发现
使用 Spring Cloud 中的 Eureka 实现服务注册与发现。每个租户的服务都在注册中心以不同的应用名称进行注册,客户端可以通过服务名称来访问对应租户的服务。
# 2.2 配置中心
使用 Spring Cloud Config 作为配置中心。配置文件以租户 ID 进行区分,客户端通过读取对应租户的配置文件来获取配置信息。
# 2.3 负载均衡
使用 Spring Cloud Ribbon 作为负载均衡器。根据请求的 URL 或参数选择对应租户的服务实例进行请求转发。
# 2.4 API
在 API 网关层面实现多租户机制根据请求的 URL 或参数判断所属租户,并转发到对应租户的服务实例。
# 四、 应用场景
# 1. 私有云环境
私有云环境指的是由企业自行搭建的云环境,不对外提供服务,主要应用于企业内部的数据存储、管理、共享和安全控制。相较于公有云,私有云的优点在于可以更好地保护企业核心数据,同时也能够满足企业对于数据安全性和可控性的要求。
# 2. 公有云环境
公有云环境指的是由云服务商搭建并对外提供服务的云环境,用户可以根据需要购买相应的云服务,如云存储、云计算、云数据库等。相较于私有云,公有云的优点在于具有成本低廉、弹性伸缩、全球化部署等特点,能够更好地满足企业快速发展的需求。
# 3. 企业级应用
企业级应用是指面向企业客户的应用程序,主要包括 ERP、CRM、OA 等一系列应用系统。这类应用的特点在于功能强大、流程复杂、数据量大,需要满足企业的高效率、高可靠性、高安全性和易维护性等要求。在云计算环境下,企业可以将这些应用部署在私有云或公有云上,减少了硬件设备的投入和维护成本,提高了管理效率。
# 五、实现步骤
# 1. 搭建 Spring Boot 和 Spring Cloud 环境
首先需要在 Maven 项目中引入以下依赖:
<!-- 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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
然后需要在 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: "*"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
其中datasource.url
为数据库连接的 URL,username 和 password 为数据库连接的账号和密码;server.port
为 Spring Boot 应用启动的端口;eureka.client.serviceUrl.defaultZone
为 Eureka 服务注册中心的 URL。
# 2. 修改数据库设计
接下来需要对数据库进行相应的修改,以支持多租户部署。具体来说,我们需要在数据库中添加一个与租户相关的字段,以便在应用中区分不同的租户。
# 3. 实现应用多租户部署
接着需要在代码中实现应用的多租户部署功能。具体来说,我们需要为每个租户实例化对应的 Spring Bean,并根据租户 ID 将请求路由到相应的 Bean 中去处理。
以下是一个简单的实现示例:
@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();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
其中MultiTenantConfig
是多租户部署的核心配置类,它提供了对应租户数据源、多租户 Session 工厂、动态切换租户等功能。
# 4. 实现租户管理
最后需要实现一个租户管理的功能,以便在系统中管理不同的租户。具体来说,我们可以使用 Spring Cloud 的服务注册与发现组件 Eureka 来注册每个租户的实例,并在管理界面中进行相应的操作。另外,我们还需要为每个租户提供一个独立的数据库,以保证数据隔离性。
# 六、小结回顾
本文详细介绍了如何使用 Spring Boot 和 Spring Cloud 实现一个支持多租户部署的应用。主要包括搭建 Spring Boot 和 Spring Cloud 环境、修改数据库设计、实现应用多租户部署、实现租户管理等方面。
- 01
- idea 热部署插件 JRebel 安装及破解,不生效问题解决04-10
- 02
- spark中代码的执行位置(Driver or Executer)12-12
- 03
- 大数据技术之 SparkStreaming12-12