sharding-jdbc 分庫分表的 4種分片策略,還蠻簡單的

程式設計師小富發表於2020-11-03

上文《快速入門分庫分表中介軟體 Sharding-JDBC (必修課)》中介紹了 sharding-jdbc 的基礎概念,還搭建了一個簡單的資料分片案例,但實際開發場景中要遠比這複雜的多,我們會按 SQL 中會出現的不同運算子 ><between andin等,來選擇對應資料分片策略。

往下開展前先做個答疑,前兩天有個小夥伴私下問了個問題說:

如果我一部分表做了分庫分表,另一部分未做分庫分表的表怎麼處理?怎麼才能正常訪問?

這是一個比較典型的問題,我們知道分庫分表是針對某些資料量持續大幅增長的表,比如使用者表、訂單表等,而不是一刀切將全部表都做分片。那麼不分片的表和分片的表如何劃分,一般有兩種解決方案。

  • 嚴格劃分功能庫,分片的庫與不分片的庫剝離開,業務程式碼中按需切換資料來源訪問
  • 設定預設資料來源,以 Sharding-JDBC 為例,不給未分片表設定分片規則,它們就不會執行,因為找不到路由規則,這時我們設定一個預設資料來源,在找不到規則時一律訪問預設庫。
# 配置資料來源 ds-0
spring.shardingsphere.datasource.ds-0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds-0.driverClassName=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds-0.url=jdbc:mysql://47.94.6.5:3306/ds-0?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
spring.shardingsphere.datasource.ds-0.username=root
spring.shardingsphere.datasource.ds-0.password=root

# 預設資料來源,未分片的表預設執行庫
spring.shardingsphere.sharding.default-data-source-name=ds-0

這篇我們針對具體的SQL使用場景,實踐一下4種分片策略的用法,開始前先做點準備工作。

  • 標準分片策略

  • 複合分片策略

  • 行表示式分片策略

  • Hint分片策略

準備工作

先建立兩個資料庫 ds-0ds-1,兩個庫中分別建表 t_order_0t_order_1t_order_2t_order_item_0t_order_item_1t_order_item_2 6張表,下邊實操看看如何在不同場景下應用 sharding-jdbc 的 4種分片策略。

t_order_n 表結構如下:

CREATE TABLE `t_order_0` (
  `order_id` bigint(200) NOT NULL,
  `order_no` varchar(100) DEFAULT NULL,
  `user_id` bigint(200) NOT NULL,
  `create_name` varchar(50) DEFAULT NULL,
  `price` decimal(10,2) DEFAULT NULL,
  PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

t_order_item_n 表結構如下:

CREATE TABLE `t_order_item_0` (
  `item_id` bigint(100) NOT NULL,
  `order_id` bigint(200) NOT NULL,
  `order_no` varchar(200) NOT NULL,
  `item_name` varchar(50) DEFAULT NULL,
  `price` decimal(10,2) DEFAULT NULL,
  PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

分片策略分為分表策略分庫策略,它們實現分片演算法的方式基本相同,不同是一個對庫ds-0ds-1,一個對錶 t_order_0 ··· t_order_n 等做處理。

標準分片策略

使用場景:SQL 語句中有>>=, <=<=INBETWEEN AND 運算子,都可以應用此分片策略。

標準分片策略(StandardShardingStrategy),它只支援對單個分片健(欄位)為依據的分庫分表,並提供了兩種分片演算法 PreciseShardingAlgorithm(精準分片)和 RangeShardingAlgorithm(範圍分片)。

在使用標準分片策略時,精準分片演算法是必須實現的演算法,用於 SQL 含有 =IN 的分片處理;範圍分片演算法是非必選的,用於處理含有 BETWEEN AND 的分片處理。

一旦我們沒配置範圍分片演算法,而 SQL 中又用到 BETWEEN AND 或者 like等,那麼 SQL 將按全庫、表路由的方式逐一執行,查詢效能會很差需要特別注意。

接下來自定義實現 精準分片演算法範圍分片演算法

1、精準分片演算法

1.1 精準分庫演算法

實現自定義精準分庫、分表演算法的方式大致相同,都要實現 PreciseShardingAlgorithm 介面,並重寫 doSharding() 方法,只是配置稍有不同,而且它只是個空方法,得我們自行處理分庫、分表邏輯。其他分片策略亦如此

SELECT * FROM t_order where  order_id = 1 or order_id in1,2,3;

下邊我們實現精準分庫策略,透過對分片健 order_id 取模的方式(怎麼實現看自己喜歡)計算出 SQL 該路由到哪個庫,計算出的分片庫資訊會存放在分片上下文中,方便後續分表中使用。

/**
 * @author xiaofu 公眾號【程式設計師內點事】
 * @description 自定義標準分庫策略
 * @date 2020/10/30 13:48
 */
public class MyDBPreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {

    @Override
    public String doSharding(Collection<String> databaseNames, PreciseShardingValue<Long> shardingValue) {

        /**
         * databaseNames 所有分片庫的集合
         * shardingValue 為分片屬性,其中 logicTableName 為邏輯表,columnName 分片健(欄位),value 為從 SQL 中解析出的分片健的值
         */
        for (String databaseName : databaseNames) {
            String value = shardingValue.getValue() % databaseNames.size() + "";
            if (databaseName.endsWith(value)) {
                return databaseName;
            }
        }
        throw new IllegalArgumentException();
    }
}

其中 Collection<String> 引數在幾種分片策略中使用一致,在分庫時值為所有分片庫的集合 databaseNames,分表時為對應分片庫中所有分片表的集合 tablesNamesPreciseShardingValue 為分片屬性,其中 logicTableName 為邏輯表,columnName 分片健(欄位),value 為從 SQL 中解析出的分片健的值。

application.properties 配置檔案中只需修改分庫策略名 database-strategy 為標準模式 standard,分片演算法 standard.precise-algorithm-class-name 為自定義的精準分庫演算法類路徑。

### 分庫策略
# 分庫分片健
spring.shardingsphere.sharding.tables.t_order.database-strategy.standard.sharding-column=order_id
# 分庫分片演算法
spring.shardingsphere.sharding.tables.t_order.database-strategy.standard.precise-algorithm-class-name=com.xiaofu.sharding.algorithm.dbAlgorithm.MyDBPreciseShardingAlgorithm

1.2 精準分表演算法

精準分表演算法同樣實現 PreciseShardingAlgorithm 介面,並重寫 doSharding() 方法。

/**
 * @author xiaofu 公眾號【程式設計師內點事】
 * @description 自定義標準分表策略
 * @date 2020/10/30 13:48
 */
public class MyTablePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {

    @Override
    public String doSharding(Collection<String> tableNames, PreciseShardingValue<Long> shardingValue) {

        /**
         * tableNames 對應分片庫中所有分片表的集合
         * shardingValue 為分片屬性,其中 logicTableName 為邏輯表,columnName 分片健(欄位),value 為從 SQL 中解析出的分片健的值
         */
        for (String tableName : tableNames) {
            /**
             * 取模演算法,分片健 % 表數量
             */
            String value = shardingValue.getValue() % tableNames.size() + "";
            if (tableName.endsWith(value)) {
                return tableName;
            }
        }
        throw new IllegalArgumentException();
    }
}

分表時 Collection<String> 引數為上邊計算出的分片庫,對應的所有分片表的集合 tablesNamesPreciseShardingValue 為分片屬性,其中 logicTableName 為邏輯表,columnName 分片健(欄位),value 為從 SQL 中解析出的分片健的值。

application.properties 配置檔案也只需修改分表策略名 database-strategy 為標準模式 standard,分片演算法 standard.precise-algorithm-class-name 為自定義的精準分表演算法類路徑。

# 分表策略
# 分表分片健
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.sharding-column=order_id
# 分表演算法
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.precise-algorithm-class-name=com.xiaofu.sharding.algorithm.tableAlgorithm.MyTablePreciseShardingAlgorithm

看到這不難發現,自定義分庫和分表演算法的實現基本是一樣的,所以後邊我們只演示分庫即可

2、範圍分片演算法

使用場景:當我們 SQL中的分片健欄位用到 BETWEEN AND運算子會使用到此演算法,會根據 SQL中給出的分片健值範圍值處理分庫、分表邏輯。

SELECT * FROM t_order where  order_id BETWEEN 1 AND 100;

自定義範圍分片演算法需實現 RangeShardingAlgorithm 介面,重寫 doSharding() 方法,下邊我透過遍歷分片健值區間,計算每一個分庫、分表邏輯。

/**
 * @author xinzhifu
 * @description 範圍分庫演算法
 * @date 2020/11/2 12:06
 */
public class MyDBRangeShardingAlgorithm implements RangeShardingAlgorithm<Integer> {

    @Override
    public Collection<String> doSharding(Collection<String> databaseNames, RangeShardingValue<Integer> rangeShardingValue) {

        Set<String> result = new LinkedHashSet<>();
        // between and 的起始值
        int lower = rangeShardingValue.getValueRange().lowerEndpoint();
        int upper = rangeShardingValue.getValueRange().upperEndpoint();
        // 迴圈範圍計算分庫邏輯
        for (int i = lower; i <= upper; i++) {
            for (String databaseName : databaseNames) {
                if (databaseName.endsWith(i % databaseNames.size() + "")) {
                    result.add(databaseName);
                }
            }
        }
        return result;
    }
}

和上邊的一樣 Collection<String> 在分庫、分表時分別代表分片庫名和表名集合,RangeShardingValue 這裡取值方式稍有不同, lowerEndpoint 表示起始值, upperEndpoint 表示截止值。

在配置上由於範圍分片演算法和精準分片演算法,同在標準分片策略下使用,所以只需新增上 range-algorithm-class-name 自定義範圍分片演算法類路徑即可。

# 精準分片演算法
spring.shardingsphere.sharding.tables.t_order.database-strategy.standard.precise-algorithm-class-name=com.xiaofu.sharding.algorithm.dbAlgorithm.MyDBPreciseShardingAlgorithm
# 範圍分片演算法
spring.shardingsphere.sharding.tables.t_order.database-strategy.standard.range-algorithm-class-name=com.xiaofu.sharding.algorithm.dbAlgorithm.MyDBRangeShardingAlgorithm

複合分片策略

使用場景:SQL 語句中有>>=, <=<=INBETWEEN AND 等運算子,不同的是複合分片策略支援對多個分片健操作。

下面我們實現同時以 order_iduser_id 兩個欄位作為分片健,自定義複合分片策略。

 SELECT * FROM t_order where  user_id =0  and order_id = 1;

我們先修改一下原配置,complex.sharding-column 切換成 complex.sharding-columns 複數,分片健上再加一個 user_id ,分片策略名變更為 complexcomplex.algorithm-class-name 替換成我們自定義的複合分片演算法。

### 分庫策略
# order_id,user_id 同時作為分庫分片健
spring.shardingsphere.sharding.tables.t_order.database-strategy.complex.sharding-column=order_id,user_id
# 複合分片演算法
spring.shardingsphere.sharding.tables.t_order.database-strategy.complex.algorithm-class-name=com.xiaofu.sharding.algorithm.dbAlgorithm.MyDBComplexKeysShardingAlgorithm

自定義複合分片策略要實現 ComplexKeysShardingAlgorithm 介面,重新 doSharding()方法。

/**
 * @author xiaofu 公眾號【程式設計師內點事】
 * @description 自定義複合分庫策略
 * @date 2020/10/30 13:48
 */
public class MyDBComplexKeysShardingAlgorithm implements ComplexKeysShardingAlgorithm<Integer> {


    @Override
    public Collection<String> doSharding(Collection<String> databaseNames, ComplexKeysShardingValue<Integer> complexKeysShardingValue) {

        // 得到每個分片健對應的值
        Collection<Integer> orderIdValues = this.getShardingValue(complexKeysShardingValue, "order_id");
        Collection<Integer> userIdValues = this.getShardingValue(complexKeysShardingValue, "user_id");

        List<String> shardingSuffix = new ArrayList<>();
        // 對兩個分片健同時取模的方式分庫
        for (Integer userId : userIdValues) {
            for (Integer orderId : orderIdValues) {
                String suffix = userId % 2 + "_" + orderId % 2;
                for (String databaseName : databaseNames) {
                    if (databaseName.endsWith(suffix)) {
                        shardingSuffix.add(databaseName);
                    }
                }
            }
        }
        return shardingSuffix;
    }

    private Collection<Integer> getShardingValue(ComplexKeysShardingValue<Integer> shardingValues, final String key) {
        Collection<Integer> valueSet = new ArrayList<>();
        Map<String, Collection<Integer>> columnNameAndShardingValuesMap = shardingValues.getColumnNameAndShardingValuesMap();
        if (columnNameAndShardingValuesMap.containsKey(key)) {
            valueSet.addAll(columnNameAndShardingValuesMap.get(key));
        }
        return valueSet;
    }
}

Collection<String> 用法還是老樣子,由於支援多分片健 ComplexKeysShardingValue 分片屬性內用一個分片健為 key,分片健值為 valuemap來儲存分片鍵屬性。

行表示式分片策略

行表示式分片策略(InlineShardingStrategy),在配置中使用 Groovy 表示式,提供對 SQL語句中的 =IN 的分片操作支援,它只支援單分片健。

行表示式分片策略適用於做簡單的分片演算法,無需自定義分片演算法,省去了繁瑣的程式碼開發,是幾種分片策略中最為簡單的。

它的配置相當簡潔,這種分片策略利用inline.algorithm-expression書寫表示式。

比如:ds-$->{order_id % 2} 表示對 order_id 做取模計算,$ 是個萬用字元用來承接取模結果,最終計算出分庫ds-0 ··· ds-n,整體來說比較簡單。

# 行表示式分片鍵
sharding.jdbc.config.sharding.tables.t_order.database-strategy.inline.sharding-column=order_id
# 表示式演算法
sharding.jdbc.config.sharding.tables.t_order.database-strategy.inline.algorithm-expression=ds-$->{order_id % 2}

Hint分片策略

Hint分片策略(HintShardingStrategy)相比於上面幾種分片策略稍有不同,這種分片策略無需配置分片健,分片健值也不再從 SQL中解析,而是由外部指定分片資訊,讓 SQL在指定的分庫、分表中執行。ShardingSphere 透過 Hint API實現指定操作,實際上就是把分片規則tableruledatabaserule由集中配置變成了個性化配置。

舉個例子,如果我們希望訂單表t_orderuser_id 做分片健進行分庫分表,但是 t_order 表中卻沒有 user_id 這個欄位,這時可以透過 Hint API 在外部手動指定分片健或分片庫。

下邊我們這邊給一條無分片條件的SQL,看如何指定分片健讓它路由到指定庫表。

SELECT * FROM t_order;

使用 Hint分片策略同樣需要自定義,實現 HintShardingAlgorithm 介面並重寫 doSharding()方法。

/**
 * @author xinzhifu
 * @description hit分表演算法
 * @date 2020/11/2 12:06
 */
public class MyTableHintShardingAlgorithm implements HintShardingAlgorithm<String> {

    @Override
    public Collection<String> doSharding(Collection<String> tableNames, HintShardingValue<String> hintShardingValue) {

        Collection<String> result = new ArrayList<>();
        for (String tableName : tableNames) {
            for (String shardingValue : hintShardingValue.getValues()) {
                if (tableName.endsWith(String.valueOf(Long.valueOf(shardingValue) % tableNames.size()))) {
                    result.add(tableName);
                }
            }
        }
        return result;
    }
}

自定義完演算法只實現了一部分,還需要在呼叫 SQL 前透過 HintManager 指定分庫、分表資訊。由於每次新增的規則都放在 ThreadLocal 內,所以要先執行 clear() 清除掉上一次的規則,否則會報錯;addDatabaseShardingValue 設定分庫分片健鍵值,addTableShardingValue設定分表分片健鍵值。setMasterRouteOnly 讀寫分離強制讀主庫,避免造成主從複製導致的延遲。

// 清除掉上一次的規則,否則會報錯
HintManager.clear();
// HintManager API 工具類例項
HintManager hintManager = HintManager.getInstance();
// 直接指定對應具體的資料庫
hintManager.addDatabaseShardingValue("ds",0);
// 設定表的分片健
hintManager.addTableShardingValue("t_order" , 0);
hintManager.addTableShardingValue("t_order" , 1);
hintManager.addTableShardingValue("t_order" , 2);

// 在讀寫分離資料庫中,Hint 可以強制讀主庫
hintManager.setMasterRouteOnly();

debug 除錯看到,我們對 t_order 表設定分表分片健鍵值,可以在自定義的演算法 HintShardingValue 引數中成功拿到。

properties 檔案中配置無需再指定分片健,只需自定義的 Hint分片演算法類路徑即可。

# Hint分片演算法
spring.shardingsphere.sharding.tables.t_order.table-strategy.hint.algorithm-class-name=com.xiaofu.sharding.algorithm.tableAlgorithm.MyTableHintShardingAlgorithm

接下來會對 Sharding-JDBC 的功能逐一實現,比如分散式事務、服務管理等,下一篇我們看看《分庫分表如何自定義分散式自增主鍵ID》。

案例 GitHub 地址:github.com/chengxy-nds/Springboot-...

整理了幾百本各類技術電子書,送給小夥伴們。關注公號回覆【666】自行領取。和一些小夥伴們建了一個技術交流群,一起探討技術、分享技術資料,旨在共同學習進步,如果感興趣就加入我們吧!

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章