在《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中的源代碼。
更多內容可以關注我的公衆號:程序員向架構師轉型。