上文《快速入門分庫分表中介軟體 Sharding-JDBC (必修課)》中介紹了 sharding-jdbc
的基礎概念,還搭建了一個簡單的資料分片案例,但實際開發場景中要遠比這複雜的多,我們會按 SQL
中會出現的不同運算子 >
、<
、between and
、in
等,來選擇對應資料分片策略。
往下開展前先做個答疑,前兩天有個小夥伴私下問了個問題說:
如果我一部分表做了分庫分表,另一部分未做分庫分表的表怎麼處理?怎麼才能正常訪問?
這是一個比較典型的問題,我們知道分庫分表是針對某些資料量持續大幅增長的表,比如使用者表、訂單表等,而不是一刀切將全部表都做分片。那麼不分片的表和分片的表如何劃分,一般有兩種解決方案。
- 嚴格劃分功能庫,分片的庫與不分片的庫剝離開,業務程式碼中按需切換資料來源訪問
- 設定預設資料來源,以
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-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
表結構如下:
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-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()
方法,只是配置稍有不同,而且它只是個空方法,得我們自行處理分庫、分表邏輯。其他分片策略亦如此。
SELECT * FROM t_order where order_id = 1 or order_id in (1,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
,分表時為對應分片庫中所有分片表的集合 tablesNames
;PreciseShardingValue
為分片屬性,其中 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>
引數為上邊計算出的分片庫,對應的所有分片表的集合 tablesNames
;PreciseShardingValue
為分片屬性,其中 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 語句中有>
,>=
, <=
,<
,=
,IN
和 BETWEEN AND
等運算子,不同的是複合分片策略支援對多個分片健操作。
下面我們實現同時以 order_id
、user_id
兩個欄位作為分片健,自定義複合分片策略。
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
替換成我們自定義的複合分片演算法。
### 分庫策略
# 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
,分片健值為 value
的 map
來儲存分片鍵屬性。
行表示式分片策略
行表示式分片策略(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實現指定操作,實際上就是把分片規則tablerule
、databaserule
由集中配置變成了個性化配置。
舉個例子,如果我們希望訂單表t_order
用 user_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 協議》,轉載必須註明作者和本文連結