ShardingSphere源码分析3-路由引擎

839 阅读4分钟

目标

继续本系列的第3篇分析文章:分析【路由引擎】的源码。

上一篇文章里,通过分析【解析引擎】的源码,我们了解到解析后得到的sqlStatement里,包含了提炼分片所需的上下文信息,也就是为下一步的【路由引擎】服务。

那么,【路由引擎】具体是如何工作的呢?通过分析源码,我们试图去找出答案

查阅文档,快速入门

首先,通过查阅官方文档,我们了解到ShardingSphere的多种路由策略,以及匹配的场景,一图以蔽之:

image.png

概念解释

在官方介绍中,出现了很多专业术语,如:分片键、分片算法、Hint、强制分片路由等,初学者一定要先搞清这些概念,才能更好地理解【路由引擎】的工作原理。

上述的术语解释,参照:shardingsphere.apache.org/document/cu…

另外的:

绑定表:
指分片规则一致的主表和子表。
例如: t_order 表和 t_order_item 表,均按照 order_id 分片,绑定表之间的分区键完全相同,则此两张表互为绑定表关系。
绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。

解析源码

经过上一步,我们了解到:【路由引擎】是根据解析上下文匹配数据库和表的分片策略,并生成路由路径。

接下来,我们通过分析源码,来找出【路由引擎】的工作过程:

// 选取入口,打上断点,执行路由后,查看返回的路径信息
public ExecutionContext generateExecutionContext(final LogicSQL logicSQL, final ShardingSphereMetaData metaData, final ConfigurationProperties props) {
    // 路由
    RouteContext routeContext = route(logicSQL, metaData, props);
    // 改写
    SQLRewriteResult rewriteResult = rewrite(logicSQL, metaData, props, routeContext);
    // 执行
    ExecutionContext result = createExecutionContext(logicSQL, metaData, routeContext, rewriteResult);
    // 日志打印
    logSQL(logicSQL, props, result);
    return result;
}

1. 获取执行器,开始执行

public RouteContext route(final LogicSQL logicSQL, final ShardingSphereMetaData metaData) {
    // 根据sqlStatement判断:用全路由执行器or部分路由执行器,条件为:sqlStatement instanceof MySQLShowTablesStatement?
    SQLRouteExecutor executor = isNeedAllSchemas(logicSQL.getSqlStatementContext().getSqlStatement()) ? new AllSQLRouteExecutor() : new PartialSQLRouteExecutor(rules, props);
    return executor.route(logicSQL, metaData);
}

2. 获取配置的分片规则后,进一步验证、合并

public RouteContext createRouteContext(final LogicSQL logicSQL, final ShardingSphereMetaData metaData, final ShardingRule rule, final ConfigurationProperties props) {
    RouteContext result = new RouteContext();
    SQLStatement sqlStatement = logicSQL.getSqlStatementContext().getSqlStatement();
    // 拿到分片规则
    ShardingConditions shardingConditions = createShardingConditions(logicSQL, metaData, rule);
    // 验证器验证分片规则
    Optional<ShardingStatementValidator> validator = ShardingStatementValidatorFactory.newInstance(sqlStatement, shardingConditions);
    validator.ifPresent(v -> v.preValidate(rule, logicSQL.getSqlStatementContext(), logicSQL.getParameters(), metaData.getSchema()));
    /*
     * 判断当前的分片规则是否需要合并
     * 合并条件:
     * 1、DML语句
     * 2、是查询语句且包含子查询
     */
    if (sqlStatement instanceof DMLStatement && shardingConditions.isNeedMerge()) {
        // 只保留最后一个规则
        shardingConditions.merge();
    }
    // 获取对应的策略后,执行路由策略,并把结果写入result变量
    ShardingRouteEngineFactory.newInstance(rule, metaData, logicSQL.getSqlStatementContext(), shardingConditions, props).route(result, rule);
    validator.ifPresent(v -> v.postValidate(rule, logicSQL.getSqlStatementContext(), result, metaData.getSchema()));
    return result;
}

3. 执行分片规则

public void route(final RouteContext routeContext, final ShardingRule shardingRule) {
    /**
     * DataNode:路由后的路径信息,包括DB、table
     */
    Collection<DataNode> dataNodes = getDataNodes(shardingRule, shardingRule.getTableRule(logicTableName));
    routeContext.getOriginalDataNodes().addAll(originalDataNodes);
    for (DataNode each : dataNodes) {
        // 遍历返回的数据节点,加到【路由上下文】里
        routeContext.getRouteUnits().add(
                new RouteUnit(new RouteMapper(each.getDataSourceName(), each.getDataSourceName()), Collections.singleton(new RouteMapper(logicTableName, each.getTableName()))));
    }
}

3.1. 获取分库规则、分表规则

private Collection<DataNode> getDataNodes(final ShardingRule shardingRule, final TableRule tableRule) {
    // 分库策略
    ShardingStrategy databaseShardingStrategy = createShardingStrategy(shardingRule.getDatabaseShardingStrategyConfiguration(tableRule),
            shardingRule.getShardingAlgorithms(), shardingRule.getDefaultShardingColumn());
    // 分表策略
    ShardingStrategy tableShardingStrategy = createShardingStrategy(shardingRule.getTableShardingStrategyConfiguration(tableRule),
            shardingRule.getShardingAlgorithms(), shardingRule.getDefaultShardingColumn());
    // 是否走强制分片策略:库跟表都是hint(优先级最高)
    if (isRoutingByHint(shardingRule, tableRule)) {
        return routeByHint(tableRule, databaseShardingStrategy, tableShardingStrategy);
    }
    // 是否走标准路由,库跟表都不是hint
    if (isRoutingByShardingConditions(shardingRule, tableRule)) {
        return routeByShardingConditions(shardingRule, tableRule, databaseShardingStrategy, tableShardingStrategy);
    }
    return routeByMixedConditions(shardingRule, tableRule, databaseShardingStrategy, tableShardingStrategy);
}

3.2. 判断是否直接路由

private boolean isRoutingByHint(final ShardingRule shardingRule, final TableRule tableRule) {
    // 配置了hint策略
    return shardingRule.getDatabaseShardingStrategyConfiguration(tableRule) instanceof HintShardingStrategyConfiguration
            && shardingRule.getTableShardingStrategyConfiguration(tableRule) instanceof HintShardingStrategyConfiguration;
}

3.3. 先执行分库,再执行分表

private Collection<DataNode> route0(final TableRule tableRule, 
                                    final ShardingStrategy databaseShardingStrategy, final List<ShardingConditionValue> databaseShardingValues, 
                                    final ShardingStrategy tableShardingStrategy, final List<ShardingConditionValue> tableShardingValues) {
    // 先执行数据库的路由策略,得到结果
    Collection<String> routedDataSources = routeDataSources(tableRule, databaseShardingStrategy, databaseShardingValues);
    Collection<DataNode> result = new LinkedList<>();
    for (String each : routedDataSources) {
        // 再遍历所有数据库,执行表的路由策略,得到最终结果
        result.addAll(routeTables(tableRule, each, tableShardingStrategy, tableShardingValues));
    }
    return result;
}
private Collection<String> doSharding(final Collection<String> availableTargetNames, final ListShardingConditionValue<?> shardingValue) {
    Collection<String> result = new LinkedList<>();
    for (Comparable<?> each : shardingValue.getValues()) {
        String target;
        // 执行完配置的分片算法后,得到分片结果
        target = shardingAlgorithm.doSharding(availableTargetNames, new PreciseShardingValue(shardingValue.getTableName(), shardingValue.getColumnName(), each));
        if (null != target && availableTargetNames.contains(target)) {
            result.add(target);
        } else if (null != target && !availableTargetNames.contains(target)) {
            throw new ShardingSphereException(String.format("Route table %s does not exist, available actual table: %s", target, availableTargetNames));
        }
    }
    return result;
}

执行到这一步,我们打上断点,查看一下分片算法和分片后的路径信息:

image.png

image.png

可以看到,分片的路径是符合预期的。

3.4. 封装结果返回

4. 得到最终的路由路径

image.png

流程图

未命名文件 (1).png

总结

一言以蔽之,【路由引擎】是通过【解析上下文】匹配对应的数据库和表的分片策略,来生成路由路径。