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

上一篇中,我们看到起到承上启下作用的ShardingRouter会调用RoutingEngine获取路由结果,而在ShardingSphere中存在多种不同类型的RoutingEngine,分别针对广播(broadcast)路由、复合(complex)路由、默认数据库(defaultdb)路由、无效(ignore)路由、标准(standard)路由以及单播(unicast)路由的不同场景。ShardingSphere的官网也从是否携带分片键的维度出发梳理了各种路由场景,如下图所示:

我们无意对所有这些RoutingEngine进行详细的讨论,但在接下来的内容中,我们会花几篇文章介绍其中具有代表性的几种RoutingEngine。首当其冲的是执行标准路由的StandardRoutingEngine。

标准路由是ShardingSphere最为推荐使用的分片方式,我们引用ShardingSphere官网的一段话来对其进行说明。标准路由适用范围是不包含关联查询或仅包含绑定表(指分片规则一致的主表和子表)之间关联查询的SQL。当分片运算符是等于号时,路由结果将落入单库(表),当分片运算符是BETWEEN或IN时,则路由结果不一定落入唯一的库(表),因此一条逻辑SQL最终可能被拆分为多条用于执行的真实SQL。

举例说明,如果按照order_id的奇数和偶数进行数据分片,一个单表查询的SQL如下:

SELECT * FROM t_order WHERE order_id IN (1, 2);

那么路由的结果应为:

SELECT * FROM t_order_0 WHERE order_id IN (1, 2);

SELECT * FROM t_order_1 WHERE order_id IN (1, 2);

绑定表的关联查询与单表查询复杂度和性能相当。举例说明,如果一个包含绑定表的关联查询的SQL如下:

SELECT * FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id  WHERE order_id IN (1, 2);

那么路由的结果应为:

SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id  WHERE order_id IN (1, 2);

SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id  WHERE order_id IN (1, 2);

理解了标准路由的概念,我们来看StandardRoutingEngine,它的route方法如下所示:

@Override

public RoutingResult route() {

        …

        return generateRoutingResult(getDataNodes(shardingRule.getTableRule(logicTableName)));

    }

核心方法就是generateRoutingResult,在此之前需要先通过getDataNodes来获取数据节点信息,该方法如下所示:

    private Collection<DataNode> getDataNodes(final TableRule tableRule) {

        if (isRoutingByHint(tableRule)) {

            return routeByHint(tableRule);

        }

        if (isRoutingByShardingConditions(tableRule)) {

            return routeByShardingConditions(tableRule);

        }

        return routeByMixedConditions(tableRule);

    }

这里会根据TableRule来判断具体应该执行的路由方式,我们慢慢来展开,先看第一个判断条件isRoutingByHint的内容,如下所示:

    private boolean isRoutingByHint(final TableRule tableRule) {

        return shardingRule.getDatabaseShardingStrategy(tableRule) instanceof HintShardingStrategy && shardingRule.getTableShardingStrategy(tableRule) instanceof HintShardingStrategy;

}

这里就需要用到ShardingRule对象,我们在《ShardingSphere源码解析之路由引擎(一)》一文中知道该对象主要保存着与分片相关的各种规则信息,包括这里用到的DatabaseShardingStrategy和TableShardingStrategy。而说到这两个对象,我们就需要引入ShardingSphere中与分片相关的一个重要概念,即分片策略ShardingStrategy。

ShardingStrategy位于sharding-core-common工程的org.apache.shardingsphere.core.strategy.route包中,其定义如下所示:

public interface ShardingStrategy {   

    Collection<String> getShardingColumns();  

    Collection<String> doSharding(Collection<String> availableTargetNames, Collection<RouteValue> shardingValues);

}

可以看到ShardingStrategy中包含两个核心方法,用于指定分片的Column以及执行分片,后者会执行分片并返回目标DataSource和Table。在ShardingSphere中存在多个ShardingStrategy实例,类层结构如下所示:

如果我们翻阅这些具体ShardingStrategy的代码,会发现每个ShardingStrategy中都会包含另一个核心概念,即分片算法ShardingAlgorithm。我们发现ShardingAlgorithm是一个空接口,但包含了四个继承接口,即PreciseShardingAlgorithm、RangeShardingAlgorithm、ComplexKeysShardingAlgorithm和HintShardingAlgorithm接口,而这四个接口又分别具有一批实现类。ShardingAlgorithm的类层结构如下所示:

至此,我们可以梳理一下与RouteEngine这条线的整体代码结构,如下所示:

             

在StandardRoutingEngine中,整体结构也与上图类似。在StandardRoutingEngine中,前面在介绍的isRoutingByHint方法中会判断是否根据Hint来进行路由,其判断依据是其DatabaseShardingStrategy 和TableShardingStrategy是否都为HintShardingStrategy,如下所示:

    private boolean isRoutingByHint(final TableRule tableRule) {

        return shardingRule.getDatabaseShardingStrategy(tableRule) instanceof HintShardingStrategy && shardingRule.getTableShardingStrategy(tableRule) instanceof HintShardingStrategy;

}

在ShardingSphere中,Hint代表的是一种直接路由的方法。这是一条流程的支线。然后,我们再看isRoutingByShardingConditions判断是否根据分片条件进行路由,其判断逻辑在于DatabaseShardingStrategy和TableShardingStrategy都不是HintShardingStrategy就走这个代码分支。而最终如果isRoutingByHint和isRoutingByShardingConditions都不满足,也就是说,DatabaseShardingStrategy或TableShardingStrategy有一个是HintShardingStrategy,则执行routeByMixedConditions这一混合的路由方式。

以上三条代码分支虽然处理方式有所不同,但本质上都是获取RouteValue的集合,我们在《ShardingSphere源码解析之路由引擎(二)》中介绍路由条件ShardingCondition时知道RouteValue保存的就是用于路由的表名和列名。在获取了所需的RouteValue之后,在StandardRoutingEngine中,以上三种场景最终都会调用route0基础方法进行路由,该方法的作用就是根据这些RouteValue得出目前DataNode的集合。同样,我们也知道DataNode中保存的就是具体的目标节点,包括dataSourceName和tableName。route0方法如下所示:

    private Collection<DataNode> route0(final TableRule tableRule, final List<RouteValue> databaseShardingValues, final List<RouteValue> tableShardingValues) {

        Collection<String> routedDataSources = routeDataSources(tableRule, databaseShardingValues);

        Collection<DataNode> result = new LinkedList<>();

        for (String each : routedDataSources) {

            result.addAll(routeTables(tableRule, each, tableShardingValues));

        }

        return result;

    }   

可以看到,该方法首先路由DataSource,然后再根据每个DataSource路由Table,最终完成DataNode集合的拼装。在上述routeDataSources和routeTables方法中,最终都会分别依赖DatabaseShardingStrategy和TableShardingStrategy的doSharding方法完成背后的路由计算以获取目标DataSource以及Table。如下所示的代码就是routeDataSources中的处理方式,routeTables也是类似:

Collection<String> result = new LinkedHashSet<>(shardingRule.getDatabaseShardingStrategy(tableRule).doSharding(tableRule.getActualDatasourceNames(), databaseShardingValues));     

当获取了DataNode集合之后,我们回到StandardRoutingEngine的generateRoutingResult方法,该方法用于组装路由结果并返回一个RoutingResult:

    private RoutingResult generateRoutingResult(final Collection<DataNode> routedDataNodes) {

        RoutingResult result = new RoutingResult();

        for (DataNode each : routedDataNodes) {

            RoutingUnit routingUnit = new RoutingUnit(each.getDataSourceName());

            routingUnit.getTableUnits().add(new TableUnit(logicTableName, each.getTableName()));

            result.getRoutingUnits().add(routingUnit);

        }

        return result;

}

这部分代码的作用就是根据每个DataNode构建一个RoutingUnit对象,然后再填充RoutingUnit中的TableUnit。关于RoutingUnit和TableUnit的数据结构我们在《ShardingSphere源码解析之路由引擎(二)》中进行了介绍,这里不再展开。

至此,对标准路由引擎StandardRoutingEngine的介绍就告一段落,标准路由是ShardingSphere最为推荐使用的分片方式,在日常开发中应用也最广泛。本文对StandardRoutingEngine的介绍关注于它的主流程,而没有对其中涉及到的ShardingStrategy以及ShardingAlgorithm做具体展开,我们会在后续文章中做专门的分析。

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

发布了112 篇原创文章 · 获赞 12 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/lantian08251/article/details/104607960
今日推荐