1. 程式人生 > >sharding-jdbc 分庫分表的 4種分片策略,還蠻簡單的

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

上文[《快速入門分庫分表中介軟體 Sharding-JDBC (必修課)》](https://mp.weixin.qq.com/s?__biz=MzAxNTM4NzAyNg==&mid=2247488500&idx=1&sn=108bf704a54b0a9638e84698deb3ce4c&chksm=9b858309acf20a1fc606f6d140e9638072405011829bb8decc906a648d3f2f75441c0adac869&token=1691474648&lang=zh_CN#rd)中介紹了 `sharding-jdbc` 的基礎概念,還搭建了一個簡單的資料分片案例,但實際開發場景中要遠比這複雜的多,我們會按 `SQL` 中會出現的不同操作符 `>`、`<`、`between and`、`in`等,來選擇對應資料分片策略。 往下開展前先做個答疑,前兩天有個小夥伴私下問了個問題說: > 如果我一部分表做了分庫分表,另一部分未做分庫分表的表怎麼處理?怎麼才能正常訪問? 這是一個比較典型的問題,我們知道分庫分表是針對某些資料量持續大幅增長的表,比如使用者表、訂單表等,而不是一刀切將全部表都做分片。那麼不分片的表和分片的表如何劃分,一般有兩種解決方案。 - 嚴格劃分功能庫,分片的庫與不分片的庫剝離開,業務程式碼中按需切換資料來源訪問 - 設定預設資料來源,以 `Sharding-JDBC` 為例,不給未分片表設定分片規則,它們就不會執行,因為找不到路由規則,這時我們設定一個預設資料來源,在找不到規則時一律訪問預設庫。 ```sql # 配置資料來源 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-0`、`ds-1`,兩個庫中分別建表 `t_order_0`、`t_order_1`、`t_order_2` 、`t_order_item_0`、`t_order_item_1`、`t_order_item_2` 6張表,下邊實操看看如何在不同場景下應用 `sharding-jdbc` 的 4種分片策略。 `t_order_n` 表結構如下: ```sql 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` 表結構如下: ```sql 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-0`、`ds-1`,一個對錶 `t_order_0` ··· `t_order_n` 等做處理。 ## 標準分片策略 **使用場景**:SQL 語句中有`>`,`>=`, `<=`,`<`,`=`,`IN` 和 `BETWEEN AND` 操作符,都可以應用此分片策略。 標準分片策略(`StandardShardingStrategy`),它只支援對單個分片健(欄位)為依據的分庫分表,並提供了兩種分片演算法 `PreciseShardingAlgorithm`(精準分片)和 `RangeShardingAlgorithm`(範圍分片)。 在使用標準分片策略時,精準分片演算法是必須實現的演算法,用於 SQL 含有 `=` 和 `IN` 的分片處理;範圍分片演算法是非必選的,用於處理含有 `BETWEEN AND` 的分片處理。 > 一旦我們沒配置範圍分片演算法,而 SQL 中又用到 `BETWEEN AND` 或者 `like`等,那麼 SQL 將按全庫、表路由的方式逐一執行,查詢效能會很差需要特別注意。 接下來自定義實現 `精準分片演算法` 和 `範圍分片演算法`。 ### 1、精準分片演算法 #### 1.1 精準分庫演算法 實現自定義精準分庫、分表演算法的方式大致相同,都要實現 `PreciseShardingAlgorithm` 介面,並重寫 `doSharding()` 方法,只是配置稍有不同,而且它只是個空方法,得我們自行處理分庫、分表邏輯。**其他分片策略亦如此**。 ```sql SELECT * FROM t_order where order_id = 1 or order_id in (1,2,3); ``` 下邊我們實現精準分庫策略,通過對分片健 `order_id` 取模的方式(怎麼實現看自己喜歡)計算出 SQL 該路由到哪個庫,計算出的分片庫資訊會存放在分片上下文中,方便後續分表中使用。 ```javascript /** * @author xiaofu 公眾號【程式設計師內點事】 * @description 自定義標準分庫策略 * @date 2020/10/30 13:48 */ public class MyDBPreciseShardingAlgorithm implements PreciseShardingAlgorithm { @Override public String doSharding(Collection databaseNames, PreciseShardingValue 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` 引數在幾種分片策略中使用一致,在分庫時值為所有分片庫的集合 `databaseNames`,分表時為對應分片庫中所有分片表的集合 `tablesNames`;`PreciseShardingValue` 為分片屬性,其中 `logicTableName` 為邏輯表,`columnName` 分片健(欄位),`value` 為從 SQL 中解析出的分片健的值。 ![](https://img-blog.csdnimg.cn/20201030142257545.png?#pic_center) 而 `application.properties` 配置檔案中只需修改分庫策略名 `database-strategy` 為標準模式 `standard`,分片演算法 `standard.precise-algorithm-class-name` 為自定義的精準分庫演算法類路徑。 ```sql ### 分庫策略 # 分庫分片健 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()` 方法。 ```javascript /** * @author xiaofu 公眾號【程式設計師內點事】 * @description 自定義標準分表策略 * @date 2020/10/30 13:48 */ public class MyTablePreciseShardingAlgorithm implements PreciseShardingAlgorithm { @Override public String doSharding(Collection tableNames, PreciseShardingValue 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` 引數為上邊計算出的分片庫,對應的所有分片表的集合 `tablesNames`;`PreciseShardingValue` 為分片屬性,其中 `logicTableName` 為邏輯表,`columnName` 分片健(欄位),`value` 為從 SQL 中解析出的分片健的值。 ![](https://img-blog.csdnimg.cn/2020103017193670.png?#pic_center) `application.properties` 配置檔案也只需修改分表策略名 `database-strategy` 為標準模式 `standard`,分片演算法 `standard.precise-algorithm-class-name` 為自定義的精準分表演算法類路徑。 ```sql # 分表策略 # 分表分片健 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中給出的分片健值範圍值處理分庫、分表邏輯。 ```sql SELECT * FROM t_order where order_id BETWEEN 1 AND 100; ``` 自定義範圍分片演算法需實現 `RangeShardingAlgorithm` 介面,重寫 `doSharding()` 方法,下邊我通過遍歷分片健值區間,計算每一個分庫、分表邏輯。 ```javascript /** * @author xinzhifu * @description 範圍分庫演算法 * @date 2020/11/2 12:06 */ public class MyDBRangeShardingAlgorithm implements RangeShardingAlgorithm { @Override public Collection doSharding(Collection databaseNames, RangeShardingValue rangeShardingValue) { Set 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 ` 在分庫、分表時分別代表分片庫名和表名集合,`RangeShardingValue ` 這裡取值方式稍有不同, `lowerEndpoint ` 表示起始值, `upperEndpoint ` 表示截止值。 ![](https://img-blog.csdnimg.cn/2020110213404242.png?#pic_center) 在配置上由於範圍分片演算法和精準分片演算法,同在標準分片策略下使用,所以只需新增上 `range-algorithm-class-name ` 自定義範圍分片演算法類路徑即可。 ```sql # 精準分片演算法 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 語句中有`>`,`>=`, `<=`,`<`,`=`,`IN` 和 `BETWEEN AND` 等操作符,不同的是複合分片策略支援對多個分片健操作。 下面我們實現同時以 `order_id`、`user_id` 兩個欄位作為分片健,自定義複合分片策略。 ```sql SELECT * FROM t_order where user_id =0 and order_id = 1; ``` 我們先修改一下原配置,`complex.sharding-column` 切換成 `complex.sharding-columns ` 複數,分片健上再加一個 `user_id` ,分片策略名變更為 `complex` ,`complex.algorithm-class-name` 替換成我們自定義的複合分片演算法。 ```sql ### 分庫策略 # 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()`方法。 ```javascript /** * @author xiaofu 公眾號【程式設計師內點事】 * @description 自定義複合分庫策略 * @date 2020/10/30 13:48 */ public class MyDBComplexKeysShardingAlgorithm implements ComplexKeysShardingAlgorithm { @Override public Collection doSharding(Collection databaseNames, ComplexKeysShardingValue complexKeysShardingValue) { // 得到每個分片健對應的值 Collection orderIdValues = this.getShardingValue(complexKeysShardingValue, "order_id"); Collection userIdValues = this.getShardingValue(complexKeysShardingValue, "user_id"); List 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 getShardingValue(ComplexKeysShardingValue shardingValues, final String key) { Collection valueSet = new ArrayList<>(); Map> columnNameAndShardingValuesMap = shardingValues.getColumnNameAndShardingValuesMap(); if (columnNameAndShardingValuesMap.containsKey(key)) { valueSet.addAll(columnNameAndShardingValuesMap.get(key)); } return valueSet; } } ``` `Collection` 用法還是老樣子,由於支援多分片健 `ComplexKeysShardingValue` 分片屬性內用一個分片健為 `key`,分片健值為 `value` 的 `map`來儲存分片鍵屬性。 ![](https://img-blog.csdnimg.cn/20201102150755428.png?#pic_center) ## 行表示式分片策略 行表示式分片策略(`InlineShardingStrategy`),在配置中使用 `Groovy` 表示式,提供對 SQL語句中的 `=` 和 `IN` 的分片操作支援,它只支援單分片健。 行表示式分片策略適用於做簡單的分片演算法,無需自定義分片演算法,省去了繁瑣的程式碼開發,是幾種分片策略中最為簡單的。 它的配置相當簡潔,這種分片策略利用`inline.algorithm-expression`書寫表示式。 比如:`ds-$->{order_id % 2}` 表示對 `order_id` 做取模計算,` $ ` 是個萬用字元用來承接取模結果,最終計算出分庫`ds-0` ··· `ds-n`,整體來說比較簡單。 ```sql # 行表示式分片鍵 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實現指定操作,實際上就是把分片規則`tablerule` 、`databaserule`由集中配置變成了個性化配置。 舉個例子,如果我們希望訂單表`t_order`用 `user_id` 做分片健進行分庫分表,但是 `t_order` 表中卻沒有 `user_id` 這個欄位,這時可以通過 Hint API 在外部手動指定分片健或分片庫。 下邊我們這邊給一條無分片條件的SQL,看如何指定分片健讓它路由到指定庫表。 ```sql SELECT * FROM t_order; ``` 使用 Hint分片策略同樣需要自定義,實現 `HintShardingAlgorithm` 介面並重寫 `doSharding()`方法。 ``` /** * @author xinzhifu * @description hit分表演算法 * @date 2020/11/2 12:06 */ public class MyTableHintShardingAlgorithm implements HintShardingAlgorithm { @Override public Collection doSharding(Collection tableNames, HintShardingValue hintShardingValue) { Collection 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` 讀寫分離強制讀主庫,避免造成主從複製導致的延遲。 ```javascript // 清除掉上一次的規則,否則會報錯 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` 引數中成功拿到。 ![](https://img-blog.csdnimg.cn/20201102182639899.png?#pic_center) `properties` 檔案中配置無需再指定分片健,只需自定義的 Hint分片演算法類路徑即可。 ```sql # Hint分片演算法 spring.shardingsphere.sharding.tables.t_order.table-strategy.hint.algorithm-class-name=com.xiaofu.sharding.algorithm.tableAlgorithm.MyTableHintShardingAlgorithm ``` 接下來會對 Sharding-JDBC 的功能逐一實現,比如分散式事務、服務管理等,下一篇我們看看《分庫分表如何自定義分散式自增主鍵ID》。 >案例 GitHub 地址:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/springboot-sharding-jdbc 整理了幾百本各類技術電子書,送給小夥伴們。關注公號回覆【**666**】自行領取。和一些小夥伴們建了一個技術交流群,一起探討技術、分享技術資料,旨在共同學習進步,如果感興趣就加入我