多租戶是一種有選擇性的數據隔離技術,能夠保證系統共性的部分被共享,個性的部分被單獨隔離。html
多租戶在數據存儲上存在三種主要的方案,分別是:前端
pigx採用的是第3種方案,即共享數據庫、共享schema,在表中經過tenant_id字段來實現多租戶數據隔離java
圖片來源sql
若是把後端抽象的當作服務的集合,那麼這些服務能夠分爲多租戶相關服務與共享租戶相關服務數據庫
PIGX經過在前端請求時頭部帶上TANENT-ID報文,來肯定租戶的請求:後端
後端服務經過TenantContextHolderFilter過濾器將請求中的tenant_id值拿到(若是爲空則採用默認值爲1的租戶),並經過TenantContextHolder放入上下文中,請求下游的業務即可經過TenantContextHolder.getTenantId()獲取當前租戶安全
@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(); }
上面所講的只是請求從前端到後端時,租戶的信息是如何肯定的過程。可是尚未解決後端服務間調用時,租戶信息的傳遞問題
pigx是經過請求鏈路信息來維護租戶信息傳遞的,pigx中使用feign實現服務間內部調用,經過對RequestInterceptor實現JiyupFeignTenantInterceptor,使feign中調用服務時像上面的前端請求同樣,在頭部也帶上了上游的TANENT-ID報文:mybatis
@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
多租戶的底層實現邏輯是經過使用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,經過了解該實現邏輯能夠得出如下結論:
這就解釋了爲什麼TenantContextHolderFilter與JiyupFeignTenantInterceptor要承上啓下的保證整個服務鏈路中的租戶信息不丟失,由於任何一個服務的底層租戶數據處理邏輯都須要用到TenantContextHolder中的租戶信息
# 租戶表維護 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實現多租戶的基本原理,如下圖示簡單描述了實現過程: