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中的源代碼。

更多內容可以關注我的公衆號:程序員向架構師轉型。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章