一、多租户基本簡介
多租户是一種有選擇性的數據隔離技術,可以保證系統共性的部分被共享,個性的部分被單獨隔離。
多租户在數據存儲上存在三種主要的方案,分別是:
- 獨立數據庫 一個租户一個數據庫,這種方案的用户數據隔離級別最高,安全性最好,但成本也高。
- 共享數據庫,獨立Schema 即所有租户共享數據庫,但一個租户一個Schema。
- 共享數據庫,共享Schema 即租户共享同一個數據庫、同一個Schema,但在表中通過TenantID區分租户的數據。
二、pigx多租户實現原理
pigx採用的是第3種方案,即共享數據庫、共享schema,在表中通過tenant_id字段來實現多租户數據隔離
圖片來源
1、如何確定租户的請求
如果把後端抽象的看成服務的集合,那麼這些服務可以分為多租户相關服務與共享租户相關服務
PIGX通過在前端請求時頭部帶上TANENT-ID報文,來確定租户的請求:
後端服務通過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();
}
2、如何在微服務中傳遞租户的請求
上面所講的只是請求從前端到後端時,租户的信息是如何確定的過程。但是還沒有解決後端服務間調用時,租户信息的傳遞問題
pigx是通過請求鏈路信息來維護租户信息傳遞的,pigx中使用feign實現服務間內部調用,通過對RequestInterceptor實現PigxFeignTenantInterceptor,使feign中調用服務時像上面的前端請求一樣,在頭部也帶上了上游的TANENT-ID報文:
PigxFeignTenantInterceptor:
@Override
public void apply(RequestTemplate requestTemplate) {
if (TenantContextHolder.getTenantId() == null) {
log.error("TTL 中的 租户ID為空,feign攔截器 >> 增強失敗");
return;
}
requestTemplate.header(CommonConstants.TENANT_ID, TenantContextHolder.getTenantId().toString());
}
由此可見:
- 租户的最初來源是前端發送的報文頭
- TenantContextHolderFilter負責報文頭的承上、PigxFeignTenantInterceptor負責報文頭的啓下、TenantContextHolder是承上啓下的容器
- 服務鏈路中的所有業務代碼,得益於以上機制的幫助,直接通過TenantContextHolder.getTenantId()便可獲得當前租户
3、如何實現租户數據邏輯
多租户的底層實現邏輯是通過使用MyBatis-Plus的分頁插件(PaginationInterceptor)實現的
在MybatisPlusConfiguration中配置PaginationInterceptor
@Configuration
@ConditionalOnBean(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisPlusConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "mybatisPlus.tenantEnable", havingValue = "true", matchIfMissing = true)
public PaginationInterceptor paginationInterceptor(PigxTenantHandler 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);
}
PigxTenantHandler實現了TenantHandler,通過了解該實現邏輯可以得出以下結論:
- 實現租户數據處理邏輯時,租户ID從TenantContextHolder.getTenantId()方法中獲取
這就解釋了為何TenantContextHolderFilter與PigxFeignTenantInterceptor要承上啓下的保證整個服務鏈路中的租户信息不丟失,因為任何一個服務的底層租户數據處理邏輯都需要用到TenantContextHolder中的租户信息
- pigx平台中的租户列名統一使用“tenant\_id”
- pigx平台中的多租户表(帶tenant\_id的表)需在配置中進行配置才能生效
比如pigx-upms-biz-dev.yml中的以下配置,就是PigxTenantHandler實現類中所需要的:
# 租户表維護
pigx:
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
4、總結
以上就是pigx實現多租户的基本原理,以下圖示簡單描述了實現過程:
參考
多租户設計
mybatis-plus多租户解析器~~~~