ShardingSphere源码解析之路由引擎(四)

上一篇中,我们对标准路由引擎StandardRoutingEngine进行了介绍,标准路由是ShardingSphere最为推荐使用的分片方式,是ShardingSphere分片路由中的一种。今天我们继续介绍RoutingEngine相关的内容。在此之前,我们需要对上一篇中提到的一个概念做一些展开,这个概念就是绑定表(BindingTable)

引用ShardingSphere官网的说法,所谓绑定表是指指分片规则一致的主表和子表。例如:t_order表和t_order_item表,均按照order_id分片,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。举例说明,如果SQL为:

SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

在不配置绑定表关系时,假设分片键order_id将数值10路由至第0片,将数值11路由至第1片,那么路由后的SQL应该为4条,它们呈现为笛卡尔积:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

在配置绑定表关系后,路由的SQL应该为2条:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

其中t_order在FROM的最左侧,ShardingSphere将会以它作为整个绑定表的主表。 所有路由计算将会只使用主表的策略,那么t_order_item表的分片计算将会使用t_order的条件。故绑定表之间的分区键要完全相同。

在理解了绑定表之后,我们来看RoutingEngine工厂类RoutingEngineFactory的newInstance方法(参考《ShardingSphere源码解析之路由引擎(二)》中的描述)中的最后一个代码分支,即当所有前置的判断都不成立时会进入到最后的getShardingRoutingEngine代码分支中,如下所示:   

    private static RoutingEngine getShardingRoutingEngine(final ShardingRule shardingRule, final SQLStatementContext sqlStatementContext,  final ShardingConditions shardingConditions, final Collection<String> tableNames) {

        Collection<String> shardingTableNames = shardingRule.getShardingLogicTableNames(tableNames);

        if (1 == shardingTableNames.size() || shardingRule.isAllBindingTables(shardingTableNames)) {

            return new StandardRoutingEngine(shardingRule, shardingTableNames.iterator().next(), sqlStatementContext, shardingConditions);

        }

        // TODO config for cartesian set

        return new ComplexRoutingEngine(shardingRule, tableNames, sqlStatementContext, shardingConditions);

    }

这段代码首先根据解析出来的逻辑表获取分片表,以SELECT i.* FROM t_order o, t_order_item i WHERE o.order_id = i.order_id and o.order_id = ?这句SQL为例,则shardingTableNames应该为t_order,t_order_item。如果分片操作只涉及到一张表,或者涉及到多张表但这些表是互为绑定表的关系时,使用上一篇()中介绍的StandardRoutingEngine进行路由。基于绑定表的概念,当多表互为BindingTable关系时,每张表的路由结果是相同的,所以只要计算第一张表的分片即可。反之,如果不满足这一条件,则构建一个ComplexRoutingEngine进行路由。

这里我们来看一下代码中的isAllBindingTables如何对多表互为绑定表关系进行判定,该方法如下所示:

    public boolean isAllBindingTables(final Collection<String> logicTableNames) {

        if (logicTableNames.isEmpty()) {

            return false;

        }

        Optional<BindingTableRule> bindingTableRule = findBindingTableRule(logicTableNames);

        if (!bindingTableRule.isPresent()) {

            return false;

        }

        Collection<String> result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);

        result.addAll(bindingTableRule.get().getAllLogicTables());

        return !result.isEmpty() && result.containsAll(logicTableNames);

    }

这段代码会通过传入的logicTableNames构建一个专门的BindingTableRule,然后看最终获取的BindingTableRule中的LogicTable是否与传入的logicTableNames一致。这里构建BindingTableRule的过程实际上是根据传入的logicTableName来从ShardingRule中自身保存的Collection<BindingTableRule>获取对应的BindingTableRule,如下所示。

    public Optional<BindingTableRule> findBindingTableRule(final String logicTableName) {

        for (BindingTableRule each : bindingTableRules) {

            if (each.hasLogicTable(logicTableName)) {

                return Optional.of(each);

            }

        }

        return Optional.absent();

    }

上述代码的bindingTableRules就是ShardingRule中自身保存的BindingTableRule集合,我们在ShardingRule构造函数中发现了初始化bindingTableRules的代码,如下所示:

bindingTableRules = createBindingTableRules(shardingRuleConfig.getBindingTableGroups());       

显然,这个构建过程与规则配置对象ShardingRuleConfiguration有关。在ShardingSphere中,规则配置存在一个基础接口,即RuleConfiguration接口,但这个接口本身是空的,它具有三个实现类,ShardingRuleConfiguration是其中的一个。RuleConfiguration接口的类层次结构如下所示:

ShardingSphere源码解析之路由引擎(四)

如果基于Yaml配置文件,绑定表的配置一般会采用如下形式:

shardingRule:  

  bindingTables:

     t_order,t_order_item

针对这种配置形式,ShardingRule会对其进行解析并生成BindingTableRule对象,如下所示:

    private BindingTableRule createBindingTableRule(final String bindingTableGroup) {

        List<TableRule> tableRules = new LinkedList<>();

        for (String each : Splitter.on(",").trimResults().splitToList(bindingTableGroup)) {

            tableRules.add(getTableRule(each));

        }

        return new BindingTableRule(tableRules);

}

至此,我们终于把绑定表相关的概念以及实现方式做了介绍,也就是说完成了RoutingEngineFactory中进入到StandardRoutingEngine这条分支的介绍。如果多表不是互为BindingTable关系时,则会进入到ComplexRoutingEngine的执行分支。接下来,让我们来看一下ComplexRoutingEngine。ComplexRoutingEngine的route方法如下所示:

@Override

    public RoutingResult route() {

        Collection<RoutingResult> result = new ArrayList<>(logicTables.size());

        Collection<String> bindingTableNames = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);

        for (String each : logicTables) {

            Optional<TableRule> tableRule = shardingRule.findTableRule(each);

            if (tableRule.isPresent()) {

                if (!bindingTableNames.contains(each)) {

                    result.add(new StandardRoutingEngine(shardingRule, tableRule.get().getLogicTable(), sqlStatementContext, shardingConditions).route());

                }

                Optional<BindingTableRule> bindingTableRule = shardingRule.findBindingTableRule(each);

                if (bindingTableRule.isPresent()) {

                    bindingTableNames.addAll(Lists.transform(bindingTableRule.get().getTableRules(), new Function<TableRule, String>() {

                        @Override

                        public String apply(final TableRule input) {

                            return input.getLogicTable();

                        }

                    }));

                }

            }

        }

        if (result.isEmpty()) {

            throw new ShardingException("Cannot find table rule and default data source with logic tables: '%s'", logicTables);

        }

        if (1 == result.size()) {

            return result.iterator().next();

        }

        return new CartesianRoutingEngine(result).route();

    }

这里使用了一个bindingTableNames变量来保存互为绑定表关系的表,然后针对每个逻辑表,根据bindingTableNames进行判定,避免重复计算分片。例如,如果t_order和t_order_item是绑定表,那么一旦t_order处理过,由于t_order_item与其是绑定关系,所以就不需要再处理。对于每个所需要进行路由的分片,则使用标准路由引擎StandardRoutingEngine进行路由,并把路由结果放到result变量中。我们看到,最终result中的路由结果会输入到CartesianRoutingEngine进行笛卡尔积路由。

更多内容可以关注我的公众号:程序员向架构师转型。

ShardingSphere源码解析之路由引擎(四)

ShardingSphere源码解析之路由引擎(四)ShardingSphere源码解析之路由引擎(四) 天涯兰的博客 博客专家 发布了100 篇原创文章 · 获赞 10 · 访问量 11万+ 私信 关注
上一篇:spring boot:用shardingjdbc实现多数据源的分库分表(shardingsphere 4.1.1/spring boot 2.3.1)


下一篇:ShardingSphere源码解析之微内核架构(下)