1. sharding-jdbc源碼之?dāng)?shù)據(jù)源

阿飛Javaer,轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處,謝謝!

com.dangdang.ddframe.rdb.sharding.example.jdbc.Main剖析分庫分表配置與實(shí)現(xiàn),其部分源碼如下:

public final class Main {
    
    public static void main(final String[] args) throws SQLException {
        // step1: 配置sharding數(shù)據(jù)源
        DataSource dataSource = getShardingDataSource();
        // step2:創(chuàng)建表
        createTable(dataSource);
        // step3:插入數(shù)據(jù)
        insertData(dataSource);
        printSimpleSelect(dataSource);
        printGroupBy(dataSource);
        printHintSimpleSelect(dataSource);
        dropTable(dataSource);
    }
    ... ...
}

接下來分析第一步,即如何創(chuàng)建ShardingDataSource;

①ShardingDataSource

硬編碼創(chuàng)建ShardingDataSource的核心實(shí)現(xiàn)源碼如下:

private static ShardingDataSource getShardingDataSource() throws SQLException {
    // 構(gòu)造DataSourceRule,即key與數(shù)據(jù)源的KV對(duì);
    DataSourceRule dataSourceRule = new DataSourceRule(createDataSourceMap());
    // 建立邏輯表是t_order,實(shí)際表是t_order_0,t_order_1的TableRule
    TableRule orderTableRule = TableRule.builder("t_order").actualTables(Arrays.asList("t_order_0", "t_order_1")).dataSourceRule(dataSourceRule).build();
    // 建立邏輯表是t_order_item,實(shí)際表是t_order_item_0,t_order_item_1的TableRule
    TableRule orderItemTableRule = TableRule.builder("t_order_item").actualTables(Arrays.asList("t_order_item_0", "t_order_item_1")).dataSourceRule(dataSourceRule).build();
    ShardingRule shardingRule = ShardingRule.builder()
                .dataSourceRule(dataSourceRule)
                .tableRules(Arrays.asList(orderTableRule, orderItemTableRule))
                // 增加綁定表--綁定表代表一組表,這組表的邏輯表與實(shí)際表之間的映射關(guān)系是相同的。比如t_order與t_order_item就是這樣一組綁定表關(guān)系,它們的分庫與分表策略是完全相同的,那么可以使用它們的表規(guī)則將它們配置成綁定表,綁定表所有路由計(jì)算將會(huì)只使用主表的策略;
                .bindingTableRules(Collections.singletonList(new BindingTableRule(Arrays.asList(orderTableRule, orderItemTableRule))))
                // 指定數(shù)據(jù)庫sharding策略--根據(jù)user_id字段的值取模
                .databaseShardingStrategy(new DatabaseShardingStrategy("user_id", new ModuloDatabaseShardingAlgorithm()))
                // 指定表sharding策略--根據(jù)order_id字段的值取模
                .tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm())).build();
    return new ShardingDataSource(shardingRule);
}

// 創(chuàng)建兩個(gè)數(shù)據(jù)源,一個(gè)是ds_jdbc_0,一個(gè)是ds_jdbc_1,并綁定映射關(guān)系key
private static Map<String, DataSource> createDataSourceMap() {
    Map<String, DataSource> result = new HashMap<>(2);
    result.put("ds_jdbc_0", createDataSource("ds_jdbc_0"));
    result.put("ds_jdbc_1", createDataSource("ds_jdbc_1"));
    return result;
}

// 以dbcp組件創(chuàng)建一個(gè)數(shù)據(jù)源
private static DataSource createDataSource(final String dataSourceName) {
    BasicDataSource result = new BasicDataSource();
    result.setDriverClassName(com.mysql.jdbc.Driver.class.getName());
    result.setUrl(String.format("jdbc:mysql://localhost:3306/%s", dataSourceName));
    result.setUsername("root");
    // sharding-jdbc默認(rèn)以密碼為空的root用戶訪問,如果修改了root用戶的密碼,這里修改為真實(shí)的密碼即可;
    result.setPassword("");
    return result;
}

備注:邏輯表(LogicTable)即數(shù)據(jù)分片的邏輯表,對(duì)于水平拆分的數(shù)據(jù)庫(表),同一類表的總稱。例:訂單數(shù)據(jù)根據(jù)訂單ID取模拆分為16張表,分別是t_order_0到t_order_15,他們的邏輯表名為t_order;實(shí)際表(ActualTable)是指在分片的數(shù)據(jù)庫中真實(shí)存在的物理表。即這個(gè)示例中的t_order_0到t_order_15。摘自sharding-jdbc核心概念

分表原則

根據(jù)上面的代碼中.tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm()))這段代碼可知,分表策略通過ModuloTableShardingAlgorithm.java實(shí)現(xiàn),且是通過ShardingStrategy.java中的doSharding()方法調(diào)用,核心源碼如下:

private Collection<String> doSharding(final Collection<ShardingValue<?>> shardingValues, final Collection<String> availableTargetNames) {
    // shardingAlgorithm即sharding算法分為三種:NoneKey,SingleKey和MultipleKeys
    if (shardingAlgorithm instanceof NoneKeyShardingAlgorithm) {
        return Collections.singletonList(((NoneKeyShardingAlgorithm) shardingAlgorithm).doSharding(availableTargetNames, shardingValues.iterator().next()));
    }
    if (shardingAlgorithm instanceof SingleKeyShardingAlgorithm) {
        // 得到SingleKeyShardingAlgorithm的具體實(shí)現(xiàn),在ShardingStrategy的構(gòu)造方法中賦值
        SingleKeyShardingAlgorithm<?> singleKeyShardingAlgorithm = (SingleKeyShardingAlgorithm<?>) shardingAlgorithm;
        // ShardingValue就是sharding的列和該列的值,在這里分別為order_id和1000
        ShardingValue shardingValue = shardingValues.iterator().next();
        // sharding列的類型分為三種:SINGLE,LIST和RANGE
        switch (shardingValue.getType()) {
            // 如果是where order_id=1000,那么type就是SINGLE
            case SINGLE:
                // doEqualSharding只返回一個(gè)值,為了doSharding()返回值的統(tǒng)一,用Collections.singletonList()包裝成集合;
                return Collections.singletonList(singleKeyShardingAlgorithm.doEqualSharding(availableTargetNames, shardingValue));
            case LIST:
                return singleKeyShardingAlgorithm.doInSharding(availableTargetNames, shardingValue);
            case RANGE:
                return singleKeyShardingAlgorithm.doBetweenSharding(availableTargetNames, shardingValue);
            default:
                throw new UnsupportedOperationException(shardingValue.getType().getClass().getName());
        }
    }
    if (shardingAlgorithm instanceof MultipleKeysShardingAlgorithm) {
        return ((MultipleKeysShardingAlgorithm) shardingAlgorithm).doSharding(availableTargetNames, shardingValues);
    }
    throw new UnsupportedOperationException(shardingAlgorithm.getClass().getName());
}
  1. 如果SQL中分表列order_id條件為where order_id=?,那么shardingValue的type為SINGLE,分表邏輯走doEqualSharding();
  2. 如果SQL中分表列order_id條件為where order_id in(?, ?),那么shardingValue的type為LIST,那么分表邏輯走doInSharding();
  3. 如果SQL中分表列order_id條件為where order_id between in(?, ?),那么shardingValue的type為RANGE,那么分表邏輯走doBetweenSharding();

shardingValue的type的判斷依據(jù)如下代碼:

public ShardingValueType getType() {
    // 
    if (null != value) {
        return ShardingValueType.SINGLE;
    }
    if (!values.isEmpty()) {
        return ShardingValueType.LIST;
    }
    return ShardingValueType.RANGE;
}

表的取模核心實(shí)現(xiàn)源碼如下:

public final class ModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Integer> {
    // 分析前提,假設(shè)預(yù)期分到兩個(gè)表中[t_order_0,t_order_1],且執(zhí)行的SQL為SELECT o.* FROM t_order o where o.order_id=1001 AND o.user_id=10,那么分表列order_id的值為1001
    @Override
    public String doEqualSharding(final Collection<String> tableNames, final ShardingValue<Integer> shardingValue) {
        // 遍歷表名[t_order_0,t_order_1]
        for (String each : tableNames) {
            // 直到表名是以分表列order_id的值1001對(duì)2取模的值即1結(jié)尾,那么就是命中的表名,即t_order_1
            if (each.endsWith(shardingValue.getValue() % tableNames.size() + "")) {
                return each;
            }
        }
        throw new UnsupportedOperationException();
    }
    
    @Override
    public Collection<String> doInSharding(final Collection<String> tableNames, final ShardingValue<Integer> shardingValue) {
        Collection<String> result = new LinkedHashSet<>(tableNames.size());
        // 從這里可知,doInSharding()和doEqualSharding()的區(qū)別就是doInSharding()時(shí)分表列有多個(gè)值(shardingValue.getValues()),例如order_id的值為[1001,1002],遍歷這些值,然后每個(gè)值按照doEqualSharding()的邏輯計(jì)算表名
        for (Integer value : shardingValue.getValues()) {
            for (String tableName : tableNames) {
                if (tableName.endsWith(value % tableNames.size() + "")) {
                    result.add(tableName);
                }
            }
        }
        return result;
    }
    
    @Override
    public Collection<String> doBetweenSharding(final Collection<String> tableNames, final ShardingValue<Integer> shardingValue) {
        Collection<String> result = new LinkedHashSet<>(tableNames.size());
        // 從這里可知,doBetweenSharding()和doInSharding()的區(qū)別就是doBetweenSharding()時(shí)分表列的多個(gè)值通過shardingValue.getValueRange()得到;而doInSharding()是通過shardingValue.getValues()得到;
        Range<Integer> range = shardingValue.getValueRange();
        for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
            for (String each : tableNames) {
                if (each.endsWith(i % tableNames.size() + "")) {
                    result.add(each);
                }
            }
        }
        return result;
    }
}
  1. 如果SQL中分表列order_id條件為where order_id=?,那么分表邏輯走doEqualSharding();
  2. 如果SQL中分表列order_id條件為where order_id in(?, ?),那么分表邏輯走doInSharding();
  3. 如果SQL中分表列order_id條件為where order_id between in(?, ?),那么分表邏輯走doBetweenSharding();

這些條件判斷依據(jù)代碼如下,當(dāng)SimpleRoutingEngine中調(diào)用routeTables()進(jìn)行路由表判定時(shí)會(huì)調(diào)用下面的方法,且通過這段代碼可知,sharding列只支持=,in和between的操作:

public ShardingValue<?> getShardingValue(final List<Object> parameters) {
    List<Comparable<?>> conditionValues = getValues(parameters);
    switch (operator) {
        case EQUAL:
            return new ShardingValue<Comparable<?>>(column.getTableName(), column.getName(), conditionValues.get(0));
        case IN:
            return new ShardingValue<>(column.getTableName(), column.getName(), conditionValues);
        case BETWEEN:
            return new ShardingValue<>(column.getTableName(), column.getName(), Range.range(conditionValues.get(0), BoundType.CLOSED, conditionValues.get(1), BoundType.CLOSED));
        default:
            throw new UnsupportedOperationException(operator.getExpression());
    }
}

分庫原則

根據(jù)上面的代碼中.databaseShardingStrategy(new DatabaseShardingStrategy("user_id", new ModuloDatabaseShardingAlgorithm()))這段代碼可知,分庫策略通過ModuloDatabaseShardingAlgorithm.java實(shí)現(xiàn);
通過比較ModuloDatabaseShardingAlgorithm.javaModuloTableShardingAlgorithm.java,發(fā)現(xiàn)兩者的實(shí)現(xiàn)邏輯完全一致,小小的區(qū)別就是ModuloDatabaseShardingAlgorithm.java根據(jù)分庫的列例如user_id進(jìn)行分庫;而ModuloTableShardingAlgorithm.java根據(jù)分表的列例如order_id進(jìn)行分表;所以分庫在這里就不分析了;

說明:由于模塊sharding-jdbc-example-jdbc中的Main方法創(chuàng)建的數(shù)據(jù)庫和表數(shù)量都是2,所以ModuloDatabaseShardingAlgorithm.javaModuloTableShardingAlgorithm.java的邏輯代碼中寫死了對(duì)2取模(% 2);這樣的話,如果debug過程中,修改了數(shù)據(jù)庫和表的數(shù)量為3,或者4,改動(dòng)代碼如下所示,就會(huì)出現(xiàn)問題:

private static ShardingDataSource getShardingDataSource() throws SQLException {
    DataSourceRule dataSourceRule = new DataSourceRule(createDataSourceMap());
    TableRule orderTableRule = TableRule
            .builder("t_order")
            .actualTables(Arrays.asList("t_order_0", "t_order_1", "t_order_2"))
            .dataSourceRule(dataSourceRule)
            .build();
    TableRule orderItemTableRule = TableRule
            .builder("t_order_item")
            .actualTables(Arrays.asList("t_order_item_0", "t_order_item_1", "t_order_item_2"))
            .dataSourceRule(dataSourceRule)
            .build();
    ... ...
}

private static Map<String, DataSource> createDataSourceMap() {
    Map<String, DataSource> result = new HashMap<>(3);
    result.put("ds_jdbc_0", createDataSource("ds_jdbc_0"));
    result.put("ds_jdbc_1", createDataSource("ds_jdbc_1"));
    result.put("ds_jdbc_2", createDataSource("ds_jdbc_2"));
    return result;
}

想要糾正這個(gè)潛在的問題,只需要將源代碼中ModuloDatabaseShardingAlgorithm.java中的% 2改為% dataSourceNames.size(),ModuloTableShardingAlgorithm.java中的% 2改為% tableNames.size()即可;這么修改的前提是配置的數(shù)據(jù)源都參與分庫分表,筆者接下來的基于ssm集成sharding-jdbc(基于xml配置創(chuàng)建sharding數(shù)據(jù)源)置<rdb:sharding-rule data-sources="sj_ds_0,sj_ds_1,sj_ds_2,sj_ds_3,sj_ds_default" default-data-source="sj_ds_default">,即sj_ds_0~3參與分庫分表,而sj_ds_default不參與分庫分表,就不適合那樣修改,而需要把取模的值提取到一個(gè)公共變量;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容