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多租户解析器~~~~

相关文章
相关标签/搜索