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

《ShardingSphere源码解析之路由引擎(三)》中,我们在介绍ShardingRule对象时引出了ShardingSphere路由引擎中的两个重要概念,即ShardingStrategy以及ShardingAlgorithm,前者称为分片策略,后者称为分片算法,由于分片算法的独立性,ShardingSphere将其进行独立抽离。从关系上讲,分片策略中包含了分片算法,即:

分片策略 = 分片算法 + 分片键

我们回顾如下所示的ShardingStrategy接口定义,我们明确在分库策略里分片的资源(也就是接口定义中的Target)指的是库,在分表策略里指的是表:

public interface ShardingStrategy {   

    Collection<String> getShardingColumns();  

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

}

在ShardingSphere中,一共存在五种ShardingStrategy实现,即标准分片策略(StandardShardingStrategy)、复合分片策略(ComplexShardingStrategy)、行表达式分片策略(InlineShardingStrategy)、Hint分片策略(HintShardingStrategy)和不分片策略(NoneShardingStrategy)。今天我们就这些ShardingStrategy进行展开讨论。

1. NoneShardingStrategy

这次我们从简单的开始,先来看NoneShardingStrategy,这是一种不执行分片的策略,实现方式如下所示:

public final class NoneShardingStrategy implements ShardingStrategy {   

    private final Collection<String> shardingColumns = Collections.emptyList();   

    @Override

    public Collection<String> doSharding(final Collection<String> availableTargetNames, final Collection<RouteValue> shardingValues) {

        return availableTargetNames;

    }

}

可以看到在NoneShardingStrategy中,直接返回了输入的availableTargetNames而不执行任何具体路由操作。

2. HintShardingStrategy

接下来我们来看HintShardingStrategy,回想我们在《ShardingSphere源码解析之路由引擎(三)》中通过这个ShardingStrategy来判断是否根据Hint进行路由。我们在这里对Hint这一概念再做进一步的阐述。在关系型数据库中,Hint作为一种SQL补充语法扮演着非常重要的角色。它允许用户通过相关的语法影响SQL的执行方式,改变SQL的执行计划,从而实现对SQL进行特殊的优化。很多数据库工具也提供了特殊的Hint语法。

对于分片字段非SQL决定,而由其他外置条件决定的场景,可使用SQL Hint灵活的注入分片字段。基于HintShardingStrategy,我们可以实现通过Hint而非SQL解析的方式执行分片策略。HintShardingStrategy类如下所示:

public final class HintShardingStrategy implements ShardingStrategy {   

    @Getter

    private final Collection<String> shardingColumns;   

    private final HintShardingAlgorithm shardingAlgorithm;   

    public HintShardingStrategy(final HintShardingStrategyConfiguration hintShardingStrategyConfig) {

        Preconditions.checkNotNull(hintShardingStrategyConfig.getShardingAlgorithm(), "Sharding algorithm cannot be null.");

        shardingColumns = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);

        shardingAlgorithm = hintShardingStrategyConfig.getShardingAlgorithm();

    }   

    @SuppressWarnings("unchecked")

    @Override

    public Collection<String> doSharding(final Collection<String> availableTargetNames, final Collection<RouteValue> shardingValues) {

        ListRouteValue shardingValue = (ListRouteValue) shardingValues.iterator().next();

        Collection<String> shardingResult = shardingAlgorithm.doSharding(availableTargetNames,

                new HintShardingValue(shardingValue.getTableName(), shardingValue.getColumnName(), shardingValue.getValues()));

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

        result.addAll(shardingResult);

        return result;

    }

}

首先,我们看到这里有一个分片算法接口HintShardingAlgorithm,继承了ShardingAlgorithm接口。HintShardingAlgorithm的定义如下所示,可以看到该接口同样存在一个doSharding方法:

public interface HintShardingAlgorithm<T extends Comparable<?>> extends ShardingAlgorithm {       

    Collection<String> doSharding(Collection<String> availableTargetNames, HintShardingValue<T> shardingValue);

}

对于Hint而言,因为它实际上是对SQL执行过程的一种直接干预,所以往往根据这里指定的availableTargetNames进行直接路由,所以我们来看HintShardingAlgorithm的实现类DefaultHintShardingAlgorithm:

public final class DefaultHintShardingAlgorithm implements HintShardingAlgorithm<Integer> {   

    @Override

    public Collection<String> doSharding(final Collection<String> availableTargetNames, final HintShardingValue<Integer> shardingValue) {

        return availableTargetNames;

    }

}

可以看到这个分片算法的执行方式确实是直接返回所输入的availableTargetNames。所以对于HintShardingStrategy而言,其执行效果也就是返回所输入的availableTargetNames而已。

另一方面,我们注意到在HintShardingStrategy中,shardingAlgorithm变量的构建是通过HintShardingStrategyConfiguration配置类完成的,显然我们可以通过配置项来设置具体的HintShardingAlgorithm(尽管ShardingSphere中只实现了一种HintShardingAlgorithm,即前面介绍的DefaultHintShardingAlgorithm)

3. StandardShardingStrategy

       接下来是StandardShardingStrategy,这是一种标准分片策略,提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND, >, <, >=, <=分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。

       在介绍StandardShardingStrategy之前,我们先讨论其所涉及的两个分片算法PreciseShardingAlgorithm和RangeShardingAlgorithm。我们先来看PreciseShardingAlgorithm,该接口用于处理使用单一键作为分片键的=与IN进行分片的场景。它有两个实现类,分别是PreciseModuloDatabaseShardingAlgorithm和PreciseModuloTableShardingAlgorithm。显然,前者用于数据库级别的分片,而后者面向表操作。它们的分片方法都一样,就是使用取模(Modulo)操作。以PreciseModuloDatabaseShardingAlgorithm为例,实现如下所示:

public final class PreciseModuloDatabaseShardingAlgorithm implements PreciseShardingAlgorithm<Integer> {   

    @Override

    public String doSharding(final Collection<String> availableTargetNames, final PreciseShardingValue<Integer> shardingValue) {

        for (String each : availableTargetNames) {

            if (each.endsWith(shardingValue.getValue() % 2 + "")) {

                return each;

            }

        }

        throw new UnsupportedOperationException();

    }

}

可以看到,这里对PreciseShardingValue进行了对2的取模计算,并与传入的availableTargetNames进行比对。

而对于RangeShardingAlgorithm而言,则用于处理使用单一键作为分片键的BETWEEN AND、>、<、>=、<=进行分片的场景。RangeShardingAlgorithm同样具有两个实现类,分别为RangeModuloDatabaseShardingAlgorithm和RangeModuloTableShardingAlgorithm,它们的命名和代码风格与PreciseShardingAlgorithm的实现类非常类似。也以RangeModuloDatabaseShardingAlgorithm为例:

public final class RangeModuloDatabaseShardingAlgorithm implements RangeShardingAlgorithm<Integer> {   

    @Override

    public Collection<String> doSharding(final Collection<String> availableTargetNames, final RangeShardingValue<Integer> shardingValue) {

        Collection<String> result = new LinkedHashSet<>(availableTargetNames.size());

        for (Integer i = shardingValue.getValueRange().lowerEndpoint(); i <= shardingValue.getValueRange().upperEndpoint(); i++) {

            for (String each : availableTargetNames) {

                if (each.endsWith(i % 2 + "")) {

                    result.add(each);

                }

            }

        }

        return result;

    }

}

与PreciseModuloDatabaseShardingAlgorithm相比,这里多了一层for循环,在该循环中添加了对范围ValueRange的lowerEndpoint()到upperEndpoint()中各个值的计算和比对。

回到StandardShardingStrategy类,我们来看它的doSharding方法,如下所示:

@Override

    public Collection<String> doSharding(final Collection<String> availableTargetNames, final Collection<RouteValue> shardingValues) {

        RouteValue shardingValue = shardingValues.iterator().next();

        Collection<String> shardingResult = shardingValue instanceof ListRouteValue

                ? doSharding(availableTargetNames, (ListRouteValue) shardingValue) : doSharding(availableTargetNames, (RangeRouteValue) shardingValue);

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

        result.addAll(shardingResult);

        return result;

}

可以看到这里根据传入的shardingValues的类型分别执行不同的doSharding,如果输入的是ListRouteValue则会使用PreciseShardingAlgorithm,如下所示:

private Collection<String> doSharding(final Collection<String> availableTargetNames, final ListRouteValue<?> shardingValue) {

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

        for (Comparable<?> each : shardingValue.getValues()) {

            String target = preciseShardingAlgorithm.doSharding(availableTargetNames, new PreciseShardingValue(shardingValue.getTableName(), shardingValue.getColumnName(), each));

            if (null != target) {

                result.add(target);

            }

        }

        return result;

    }

而如果是RangeRouteValue则使用RangeShardingAlgorithm,如下所示:   

    private Collection<String> doSharding(final Collection<String> availableTargetNames, final RangeRouteValue<?> shardingValue) {

        if (null == rangeShardingAlgorithm) {

            throw new UnsupportedOperationException("Cannot find range sharding strategy in sharding rule.");

        }

        return rangeShardingAlgorithm.doSharding(availableTargetNames,

                new RangeShardingValue(shardingValue.getTableName(), shardingValue.getColumnName(), shardingValue.getValueRange()));

    }

4. ComplexShardingStrategy

最后我们来看复合分片策略ComplexShardingStrategy,该策略提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。我们先来看ComplexShardingStrategy的doSharding方法,如下所示:

    public Collection<String> doSharding(final Collection<String> availableTargetNames, final Collection<RouteValue> shardingValues) {

        Map<String, Collection<Comparable<?>>> columnShardingValues = new HashMap<>(shardingValues.size(), 1);

        Map<String, Range<Comparable<?>>> columnRangeValues = new HashMap<>(shardingValues.size(), 1);

        String logicTableName = "";

        for (RouteValue each : shardingValues) {

            if (each instanceof ListRouteValue) {

                columnShardingValues.put(each.getColumnName(), ((ListRouteValue) each).getValues());

            } else if (each instanceof RangeRouteValue) {

                columnRangeValues.put(each.getColumnName(), ((RangeRouteValue) each).getValueRange());

            }

            logicTableName = each.getTableName();

        }

        Collection<String> shardingResult = shardingAlgorithm.doSharding(availableTargetNames, new ComplexKeysShardingValue(logicTableName, columnShardingValues, columnRangeValues));

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

        result.addAll(shardingResult);

        return result;

}

这里基于传入的RouteValue分别构建了ListRouteValue和RangeRouteValue,然后传递给ComplexKeysShardingAlgorithm进行计算。

对于ComplexShardingStrategy而言,ShardingSphere的官网表明其支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。基于这一点考虑,ShardingSphere的ComplexKeysShardingAlgorithm的唯一实现类DefaultComplexKeysShardingAlgorithm显得非常简单,其代码如下所示:

public final class DefaultComplexKeysShardingAlgorithm implements ComplexKeysShardingAlgorithm<Integer> {   

    @Override

    public Collection<String> doSharding(final Collection<String> availableTargetNames, final ComplexKeysShardingValue<Integer> shardingValue) {

        return availableTargetNames;

    }

}

可以看到DefaultComplexKeysShardingAlgorithm与NoneShardingStrategy的实现实际上是一样的,相当于就是什么都没有做,也就是所有的工作都需要交给开发者自行进行设计和实现。

最后,作为总结,我们要注意所有的ShardingStrategy相关类都位于sharding-core-common工程的org.apache.shardingsphere.core.strategy包下,如下所示:

而所有的ShardingAlgorithm相关类则位于sharding-core-api工程的org.apache.shardingsphere.api.sharding包下,如下所示:

我们在前面已经提到过ShardingStrategy的创建依赖于ShardingStrategyConfiguration,ShardingSphere也提供了一个ShardingStrategyFactory工厂类用于创建各种具体的ShardingStrategy,如下所示:

public final class ShardingStrategyFactory {   

    public static ShardingStrategy newInstance(final ShardingStrategyConfiguration shardingStrategyConfig) {

        if (shardingStrategyConfig instanceof StandardShardingStrategyConfiguration) {

            return new StandardShardingStrategy((StandardShardingStrategyConfiguration) shardingStrategyConfig);

        }

        if (shardingStrategyConfig instanceof InlineShardingStrategyConfiguration) {

            return new InlineShardingStrategy((InlineShardingStrategyConfiguration) shardingStrategyConfig);

        }

        if (shardingStrategyConfig instanceof ComplexShardingStrategyConfiguration) {

            return new ComplexShardingStrategy((ComplexShardingStrategyConfiguration) shardingStrategyConfig);

        }

        if (shardingStrategyConfig instanceof HintShardingStrategyConfiguration) {

            return new HintShardingStrategy((HintShardingStrategyConfiguration) shardingStrategyConfig);

        }

        return new NoneShardingStrategy();

    }

}

而这里用到的各种ShardingStrategyConfiguration也都位于sharding-core-api工程的org.apache.shardingsphere.api.sharding.strategy包下,如下所示:

这样,通过对路由引擎的介绍,我们又接触到了一大批ShardingSphere中的源代码。

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

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

猜你喜欢

转载自blog.csdn.net/lantian08251/article/details/104667705