上文《快速入門分庫分表中間件 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 地址:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/springboot-sharding-jdbc
整理了幾百本各類技術電子書,送給小伙伴們。關注公號回復【666】自行領取。和一些小伙伴們建了一個技術交流群,一起探討技術、分享技術資料,旨在共同學習進步,如果感興趣就加入我們吧!