多租户是一种有选择性的数据隔离技术,能够保证系统共性的部分被共享,个性的部分被单独隔离。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实现多租户的基本原理,如下图示简单描述了实现过程: