在《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中的源代码。
更多内容可以关注我的公众号:程序员向架构师转型。