天天看點

Spring Boot 優雅實作多租戶架構

作者:逍遙貧道
  • 一、概述
  • 二、設計思路
  • 三、技術實作
  • 四、 應用場景
  • 五、實作步驟
  • 六、小結回顧
Spring Boot 優雅實作多租戶架構

一、概述

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;
    }
}
           

1.2 Spring Cloud

在架構多租戶的系統時Spring Cloud會更加有用。Spring Cloud提供了一些成熟的解決方案,如Eureka、Zookeeper、Consul等,以實作服務發現、負載均衡等微服務功能。

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
);
           

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");
    }
}
           

三、技術實作

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();
    }
}
           

以上代碼是配置了三個資料源分别對應三個租戶。然後在使用時,可以使用注解标記需要連接配接的資料源。

@Service
public class ProductService {
    @Autowired
    @Qualifier("dataSourceA")
    private DataSource dataSource;

    // ...
}
           

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();
    }
}
           

以上是動态路由的核心代碼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>
           

然後需要在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 修改資料庫設計

接下來需要對資料庫進行相應的修改,以支援多租戶部署。具體來說,我們需要在資料庫中添加一個與租戶相關的字段,以便在應用中區分不同的租戶。

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();
    }
 
}
           

其中MultiTenantConfig是多租戶部署的核心配置類,它提供了對應租戶資料源、多租戶Session工廠、動态切換租戶等功能。

4 實作租戶管理

最後需要實作一個租戶管理的功能,以便在系統中管理不同的租戶。具體來說,我們可以使用Spring Cloud的服務注冊與發現元件Eureka來注冊每個租戶的執行個體,并在管理界面中進行相應的操作。另外,我們還需要為每個租戶提供一個獨立的資料庫,以保證資料隔離性。

六、小結回顧

本文詳細介紹了如何使用Spring Boot和Spring Cloud實作一個支援多租戶部署的應用。主要包括搭建Spring Boot和Spring Cloud環境、修改資料庫設計、實作應用多租戶部署、實作租戶管理等方面。

應用場景主要包括SaaS應用、多租戶雲服務等。優劣勢主要展現在提升了應用的可擴充性和可維護性,但也增加了部署和管理的複雜度。未來的改進方向可以考慮進一步提升多租戶管理的自動化程度,減少人工幹預和錯誤率。

繼續閱讀