pigx微服務開發平臺多租戶系統研究

1、多租戶基本簡介

多租戶是一種有選擇性的數據隔離技術,能夠保證系統共性的部分被共享,個性的部分被單獨隔離。html

多租戶在數據存儲上存在三種主要的方案,分別是:前端

  1. 獨立數據庫 一個租戶一個數據庫,這種方案的用戶數據隔離級別最高,安全性最好,但成本也高。
  2. 共享數據庫,獨立Schema 即全部租戶共享數據庫,但一個租戶一個Schema。
  3. 共享數據庫,共享Schema 即租戶共享同一個數據庫、同一個Schema,但在表中經過TenantID區分租戶的數據。

2、pigx多租戶實現原理

pigx採用的是第3種方案,即共享數據庫、共享schema,在表中經過tenant_id字段來實現多租戶數據隔離java

20190619224335_kHDdnQ_Screenshot.jpeg
圖片來源sql

一、如何肯定租戶的請求

若是把後端抽象的當作服務的集合,那麼這些服務能夠分爲多租戶相關服務與共享租戶相關服務數據庫

PIGX經過在前端請求時頭部帶上TANENT-ID報文,來肯定租戶的請求:
image-20200611171348120.png後端

後端服務經過TenantContextHolderFilter過濾器將請求中的tenant_id值拿到(若是爲空則採用默認值爲1的租戶),並經過TenantContextHolder放入上下文中,請求下游的業務即可經過TenantContextHolder.getTenantId()獲取當前租戶安全

TenantContextHolderFilter部分源碼:

@Override
@SneakyThrows
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    HttpServletResponse response = (HttpServletResponse) servletResponse;

    String tenantId = request.getHeader(CommonConstants.TENANT_ID);
    log.debug("獲取header中的租戶ID爲:{}", tenantId);

    if (StrUtil.isNotBlank(tenantId)) {
        TenantContextHolder.setTenantId(Integer.parseInt(tenantId));
    } else {
        // 當請求頭部未包含tenant_id值時,使用默認租戶值1:CommonConstants.TENANT_ID_1
        TenantContextHolder.setTenantId(CommonConstants.TENANT_ID_1);
    }

    filterChain.doFilter(request, response);
    TenantContextHolder.clear();
}

二、如何在微服務中傳遞租戶的請求

上面所講的只是請求從前端到後端時,租戶的信息是如何肯定的過程。可是尚未解決後端服務間調用時,租戶信息的傳遞問題
image-20200611171359423.png
pigx是經過請求鏈路信息來維護租戶信息傳遞的,pigx中使用feign實現服務間內部調用,經過對RequestInterceptor實現JiyupFeignTenantInterceptor,使feign中調用服務時像上面的前端請求同樣,在頭部也帶上了上游的TANENT-ID報文:mybatis

JiyupFeignTenantInterceptor:

@Override
public void apply(RequestTemplate requestTemplate) {
    if (TenantContextHolder.getTenantId() == null) {
        log.error("TTL 中的 租戶ID爲空,feign攔截器 >> 加強失敗");
        return;
    }
    requestTemplate.header(CommonConstants.TENANT_ID, TenantContextHolder.getTenantId().toString());
}

因而可知:app

  1. 租戶的最初來源是前端發送的報文頭
  2. TenantContextHolderFilter負責報文頭的承上、JiyupFeignTenantInterceptor報文頭的啓下、TenantContextHolder是承上啓下的容器
  3. 服務鏈路中的全部業務代碼,得益於以上機制的幫助,直接經過TenantContextHolder.getTenantId()即可得到當前租戶

三、如何實現租戶數據邏輯

多租戶的底層實現邏輯是經過使用MyBatis-Plus的分頁插件(PaginationInterceptor)實現的
在MybatisPlusConfiguration中配置PaginationInterceptoride

@Configuration
@ConditionalOnBean(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisPlusConfiguration {

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(name = "mybatisPlus.tenantEnable", havingValue = "true", matchIfMissing = true)
    public PaginationInterceptor paginationInterceptor(JiyupTenantHandler tenantHandler) {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        List<ISqlParser> sqlParserList = new ArrayList<>();
        TenantSqlParser tenantSqlParser = new TenantSqlParser();
        tenantSqlParser.setTenantHandler(tenantHandler);
        sqlParserList.add(tenantSqlParser);
        paginationInterceptor.setSqlParserList(sqlParserList);
        return paginationInterceptor;
    }
    
}

插件接收一個SQL解析器(ISqlParse)集合,每增長一種解析業務就應該向該集合中增長一個解析器的實現

這裏只加入了租戶SQL解析器(TenantSqlParser),該解析器負責實現租戶的數據處理邏輯

同時TenantSqlParser將須要由外部業務所決定的部分經過TenantHandler接口提取出來:

/**
 * 租戶處理器( TenantId 行級 )
 *
 * @author hubin
 * @since 2017-08-31
 */
public interface TenantHandler {

    /**
     * 獲取租戶 ID 值表達式,支持多個 ID 條件查詢
     * <p>
     * 支持自定義表達式,好比:tenant_id in (1,2) @since 2019-8-2
     *
     * @param where 參數 true 表示爲 where 條件 false 表示爲 insert 或者 select 條件
     * @return 租戶 ID 值表達式
     */
    Expression getTenantId(boolean where);

    /**
     * 獲取租戶字段名
     *
     * @return 租戶字段名
     */
    String getTenantIdColumn();

    /**
     * 根據表名判斷是否進行過濾
     *
     * @param tableName 表名
     * @return 是否進行過濾, true:表示忽略,false:須要解析多租戶字段
     */
    boolean doTableFilter(String tableName);
}

JiyupTenantHandler實現了TenantHandler,經過了解該實現邏輯能夠得出如下結論:

  1. 實現租戶數據處理邏輯時,租戶ID從TenantContextHolder.getTenantId()方法中獲取

    這就解釋了爲什麼TenantContextHolderFilter與JiyupFeignTenantInterceptor要承上啓下的保證整個服務鏈路中的租戶信息不丟失,由於任何一個服務的底層租戶數據處理邏輯都須要用到TenantContextHolder中的租戶信息

  2. pigx平臺中的租戶列名統一使用「tenant_id」
  3. pigx平臺中的多租戶表(帶tenant_id的表)需在配置中進行配置才能生效
    好比pigx-upms-biz-dev.yml中的如下配置,就是JiyupTenantHandler實現類中所須要的:
# 租戶表維護
jiyup:
  tenant:
    column: tenant_id
    tables:
      - sys_user
      - sys_role
      - sys_dept
      - sys_log
      - sys_social_details
      - sys_dict
      - sys_dict_item
      - sys_public_param
      - sys_log
      - sys_file

四、總結

以上就是pigx實現多租戶的基本原理,如下圖示簡單描述了實現過程:
image-20200612162523674.png
image-20200612162532298.png
image-20200612162541548.png
image-20200612162546976.png

參考

多租戶設計
mybatis-plus多租戶解析器~~~~

相關文章
相關標籤/搜索