大家好,我是小富~
前言
本文是《ShardingSphere5.x分庫分表原理與實戰》系列的第六篇,書接上文實現三種自定義分片算法。通過自定義算法,可以根據特定業務需求定製分片策略,以滿足不同場景下的性能、擴展性或數據處理需求。同時,可以優化分片算法以提升系統性能,規避數據傾斜等問題。
在這裏,自定義分片算法的類型(Type)統一爲CLASS_BASED
,包含兩個屬性:strategy
表示分片策略類型,目前支持三種:STANDARD
、COMPLEX
、HINT
;algorithmClassName
表示自定義分片算法的實現類路徑。此外,還可以向算法類內傳入自定義屬性。
自定義 STANDARD 算法
要實現自定義 STANDARD
標準算法,需要實現StandardShardingAlgorithm<T>
接口( T 代表接收的分片健值類型),並重寫接口中的四個方法。其中,有兩個 doSharding()
方法爲處理分片的核心邏輯;getProps()
方法用於獲取分片算法的配置信息;init()
方法則用於初始化分片算法的配置信息,支持動態修改。
5.X 以後的版本,實現自定義標準算法的精準分片和範圍分片,不在需要實現多個接口。只用實現 StandardShardingAlgorithm 標準算法接口,重寫兩個 doSharding() 方法。 doSharding(availableTargetNames,rangeShardingValue) 處理含有 >、<、between and 等操作符的 SQL,doSharding(availableTargetNames,preciseShardingValue) 處理含有 = 、in 等操作符的 SQL。
精準分片
精準分片用於SQL中包含 in、= 等操作符的場景,支持單一分片健。
重寫方法 doSharding(Collection availableTargetNames, PreciseShardingValue preciseShardingValue),該方法返回單一的分片數據源或分片表數據。有兩個參數:一個是可用目標分庫、分表的集合,另一個是精準分片屬性對象。
PreciseShardingValue
對象屬性數據格式如下:
{
"columnName": "order_id", // 分片健
"dataNodeInfo": {
"paddingChar": "0",
"prefix": "db", // 數據節點信息前綴,例如:分庫時爲db,分表時爲分片表t_order_
"suffixMinLength": 1
},
"logicTableName": "t_order", // 邏輯表
"value": 1 // 分片健值
}
範圍分片
範圍分片用於 SQL中包含 >、< 等範圍操作符的場景,支持單一分片健。
重寫方法 doSharding(Collection availableTargetNames, RangeShardingValue rangeShardingValue),該方法可以返回多個分片數據源或分片表數據。有兩個參數:一個是可用目標分庫、分表的集合,另一個是精準分片屬性對象。
RangeShardingValue
對象屬性數據格式如下:
{
"columnName": "order_id", // 分片健
"dataNodeInfo": {
"paddingChar": "0",
"prefix": "db", // 數據節點前綴,分庫時爲數據源,分表時爲分片表t_order_
"suffixMinLength": 1
},
"logicTableName": "t_order", // 邏輯表
"valueRange": [0,∞] // 分片健值的範圍數據
}
精準分片算法的 doSharding() 執行流程:從PreciseShardingValue.getValue()
中獲取分片鍵值,然後經過計算得出相應編號,最終在availableTargetNames
可用目標分庫、分片表集合中選擇以一個符合的返回。
範圍分片算法的 doSharding() 執行流程:從RangeShardingValue.getValueRange()
方法獲取分片鍵的數值範圍,然後經過計算得出相應編號,最終在availableTargetNames
可用目標分庫、分片表集合中選擇多個符合的返回。
下面是具體實現分片的邏輯:
/**
* 自定義標準分片算法
*
* @author 公衆號:程序員小富
* @date 2024/03/22 11:02
*/
@Slf4j
public class OrderStandardCustomAlgorithm implements StandardShardingAlgorithm<Long> {
/**
* 精準分片進入 sql中有 = 和 in 等操作符會執行
*
* @param availableTargetNames 所有分片表的集合
* @param shardingValue 分片健的值,SQL中解析出來的分片值
*/
@Override
public String doSharding(Collection<String> availableTargetNames,
PreciseShardingValue<Long> shardingValue) {
/**
* 分庫策略使用時:availableTargetNames 參數數據爲分片庫的集合 ["db0","db1"]
* 分表策略使用時:availableTargetNames 參數數據爲分片庫的集合 ["t_order_0","t_order_1","t_order_2"]
*/
log.info("進入精準分片 precise availableTargetNames:{}", JSON.toJSONString(availableTargetNames));
/**
* 分庫策略使用時: shardingValue 參數數據:{"columnName":"order_id","dataNodeInfo":{"paddingChar":"0","prefix":"db","suffixMinLength":1},"logicTableName":"t_order","value":1}
* 分表策略使用時: shardingValue 參數數據:{"columnName":"order_id","dataNodeInfo":{"paddingChar":"0","prefix":"t_order_","suffixMinLength":1},"logicTableName":"t_order","value":1}
*/
log.info("進入精準分片 preciseShardingValue:{}", JSON.toJSONString(shardingValue));
int tableSize = availableTargetNames.size();
// 真實表的前綴
String tablePrefix = shardingValue.getDataNodeInfo().getPrefix();
// 分片健的值
long orderId = shardingValue.getValue();
// 對分片健取模後確定位置
long mod = orderId % tableSize;
return tablePrefix + mod;
}
/**
* 範圍分片進入 sql中有 between 和 < > 等操作符會執行
*
* @param availableTargetNames 所有分片表的集合
* @param shardingValue 分片健的值,SQL中解析出來的分片值
* @return
*/
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames,
RangeShardingValue<Long> shardingValue) {
/**
* 分庫策略使用時:availableTargetNames 參數數據爲分片庫的集合 ["db0","db1"]
* 分表策略使用時:availableTargetNames 參數數據爲分片庫的集合 ["t_order_0","t_order_1","t_order_2"]
*/
log.info("進入範圍分片:range availableTargetNames:{}", JSON.toJSONString(availableTargetNames));
/**
* 分庫策略使用時 shardingValue 參數數據:{"columnName":"order_id","dataNodeInfo":{"paddingChar":"0","prefix":"db","suffixMinLength":1},"logicTableName":"t_order","valueRange":{"empty":false}}
* 分表策略使用時 shardingValue 參數數據:{"columnName":"order_id","dataNodeInfo":{"paddingChar":"0","prefix":"t_order_","suffixMinLength":1},"logicTableName":"t_order","valueRange":{"empty":false}}
*/
log.info("進入範圍分片:rangeShardingValue:{}", JSON.toJSONString(shardingValue));
// 分片健值的下邊界
Range<Long> valueRange = shardingValue.getValueRange();
Long lower = valueRange.lowerEndpoint();
// 分片健值的上邊界
Long upper = valueRange.upperEndpoint();
// 真實表的前綴
String tablePrefix = shardingValue.getDataNodeInfo().getPrefix();
if (lower != null && upper != null) {
// 分片健的值
long orderId = upper - lower;
// 對分片健取模後確定位置
long mod = orderId % availableTargetNames.size();
return Arrays.asList(tablePrefix + mod);
}
//
return Collections.singletonList("t_order_0");
}
@Override
public Properties getProps() {
return null;
}
/**
* 初始化配置
*
* @param properties
*/
@Override
public void init(Properties properties) {
Object prop = properties.get("prop");
log.info("配置信息:{}", JSON.toJSONString(prop));
}
}
配置算法
在實現了自定義分片算法的兩個 doSharding()
核心邏輯之後,接着配置並使用定義的算法。配置屬性包括strategy
分片策略類型設置成standard
,algorithmClassName
自定義標準算法的實現類全路徑。需要注意的是:策略和算法類型必須保持一致,否則會導致錯誤。
spring:
shardingsphere:
rules:
sharding:
# 分片算法定義
sharding-algorithms:
t_order_database_mod:
type: MOD
props:
sharding-count: 2 # 指定分片數量
# 12、自定義 STANDARD 標準算法
t_order_standard_custom_algorithm:
type: CLASS_BASED
props:
# 分片策略
strategy: standard
# 分片算法類
algorithmClassName: com.shardingsphere_101.algorithm.OrderStandardCustomAlgorithm
# 自定義屬性
prop:
aaaaaa: 123456
bbbbbb: 654321
tables:
# 邏輯表名稱
t_order:
# 數據節點:數據庫.分片表
actual-data-nodes: db$->{0..1}.t_order_${0..2}
# 分庫策略
database-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: t_order_database_mod
# 分表策略
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: t_order_standard_custom_algorithm
測試算法
在插入測試數據時,默認會自動進入精確分片的 doSharding() 方法內,看到該方法會獲取分片鍵的數值,根據我們的計算規則確定返回一個目標分片表用於路由。
接着執行一個範圍查詢的 SQL,此時將進入範圍分片的 doSharding() 方法。通過觀察 shardingValue.getValueRange()
方法中分片鍵的數值範圍,可以發現這些數值範圍是從SQL查詢中解析得到的。
select * from t_order where order_id > 1 and order_id < 10
自定義 COMPLEX 算法
複合分片算法支持包含 >,>=, <=,<,=,IN 和 BETWEEN AND 等操作符的SQL,支持多分片健。
自定義COMPLEX
複合分片算法,需要我們實現 ComplexKeysShardingAlgorithm<T>
接口(其中 T 代表接收的分片鍵值類型),並重寫該接口內部的 3 個方法。其中,主要關注用於處理核心分片邏輯的 doSharding()方法,可以返回多個分片數據源或分片表數據;其他兩個配置方法與上述類似,這裏不再贅述。
重寫複合分片方法 doSharding(Collection availableTargetNames, ComplexKeysShardingValue shardingValues) 實現定製的多分片健邏輯,該方法有兩個參數:一個是可用目標分庫、分表的集合;另一個是多分片健屬性對象。
logicTableName
爲邏輯表名,columnNameAndShardingValuesMap
用於存儲多個分片鍵和對應的鍵值,columnNameAndRangeValuesMap
用於存儲多個分片鍵和對應的鍵值範圍。
ComplexKeysShardingValue
數據結構如下:
public final class ComplexKeysShardingValue<T extends Comparable<?>> implements ShardingValue {
// 邏輯表
private final String logicTableName;
// 多分片健及其數值
private final Map<String, Collection<T>> columnNameAndShardingValuesMap;
// 多分片健及其範圍數值
private final Map<String, Range<T>> columnNameAndRangeValuesMap;
}
核心流程:通過循環 Map 得到多個分片健值進行計算,從 availableTargetNames 可用目標分庫、分片表集合中選擇多個符合條件的返回。
/**
* 自定義複合分片算法
*
* @author 公衆號:程序員小富
* @date 2024/03/22 11:02
*/
@Slf4j
public class OrderComplexCustomAlgorithm implements ComplexKeysShardingAlgorithm<Long> {
/**
* 複合分片算法進入,支持>,>=, <=,<,=,IN 和 BETWEEN AND 等操作符
*
* @param availableTargetNames 所有分片表的集合
* @param complexKeysShardingValue 多個分片健的值,並SQL中解析出來的分片值
*/
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames,
ComplexKeysShardingValue<Long> complexKeysShardingValue) {
/**
* 分庫策略使用時:availableTargetNames 參數數據爲分片庫的集合 ["db0","db1"]
* 分表策略使用時:availableTargetNames 參數數據爲分片庫的集合 ["t_order_0","t_order_1","t_order_2"]
*/
log.info("進入複合分片:complex availableTargetNames:{}", JSON.toJSONString(availableTargetNames));
// 多分片健和其對應的分片健範圍值
Map<String, Range<Long>> columnNameAndRangeValuesMap = complexKeysShardingValue.getColumnNameAndRangeValuesMap();
log.info("進入複合分片:columnNameAndRangeValuesMap:{}", JSON.toJSONString(columnNameAndRangeValuesMap));
columnNameAndRangeValuesMap.forEach((columnName, range) -> {
// 分片健
log.info("進入複合分片:columnName:{}", columnName);
// 分片健範圍值
log.info("進入複合分片:range:{}", JSON.toJSONString(range));
});
// 多分片健和其對應的分片健值
Map<String, Collection<Long>> columnNameAndShardingValuesMap = complexKeysShardingValue.getColumnNameAndShardingValuesMap();
log.info("進入複合分片:columnNameAndShardingValuesMap:{}", JSON.toJSONString(columnNameAndShardingValuesMap));
columnNameAndShardingValuesMap.forEach((columnName, shardingValues) -> {
// 分片健
log.info("進入複合分片:columnName:{}", columnName);
// 分片健值
log.info("進入複合分片:shardingValues:{}", JSON.toJSONString(shardingValues));
});
return null;
}
}
配置算法
處理完複合分片算法的doSharding()
核心邏輯,接着配置使用定義的算法,配置屬性包括strategy
分片策略類型設置成complex
,algorithmClassName
自定義算法的實現類全路徑。
需要注意:配置分片鍵時,一定要使用 sharding-columns 表示複數形式,很容易出錯。
spring:
shardingsphere:
rules:
sharding:
sharding-algorithms:
t_order_database_mod:
type: MOD
props:
sharding-count: 2 # 指定分片數量
# 13、自定義 complex 標準算法
t_order_complex_custom_algorithm:
type: CLASS_BASED
props:
# 分片策略
strategy: complex
# 分片算法類
algorithmClassName: com.shardingsphere_101.algorithm.OrderComplexCustomAlgorithm
# 自定義屬性
aaaaaa: aaaaaa
tables:
# 邏輯表名稱
t_order:
# 數據節點:數據庫.分片表
actual-data-nodes: db$->{0..1}.t_order_${0..2}
# 分庫策略
database-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: t_order_database_mod
# 分表策略
table-strategy:
complex:
sharding-columns: order_id , user_id
sharding-algorithm-name: t_order_complex_custom_algorithm
測試算法
插入測試數據,debug 進入 doSharding() 方法,看到columnNameAndShardingValuesMap
內獲取到了 user_id 、order_id 兩個分片鍵及健值。
當執行範圍查詢的SQL,columnNameAndRangeValuesMap
屬性內獲取到了 user_id、order_id 兩個分片鍵及健值範圍,通過range.upperEndpoint()、lowerEndpoint()得到上下界值。
select * from t_order where order_id > 1 and user_id > 1;
自定義 HINT 算法
要實現自定義HINT
強制路由分片算法,需要實現 HintShardingAlgorithm<T>
接口( T 代表接收的分片鍵值類型)。在實現過程中,需要重寫接口中的3個方法。其中,核心的分片邏輯在 doSharding() 方法中處理,可以支持返回多個分片數據源或分片表數據。另外,其他兩個prop配置方法的使用方式與上述相同,這裏不贅述。
重寫 HINT 核心分片方法 doSharding(Collection availableTargetNames, HintShardingValue shardingValue),以實現我們的定製邏輯。該方法接受兩個參數:一個是可用目標分庫、分表的集合,另一個是 Hint 分片屬性對象。
方法內執行流程:我們首先獲取 HintManager API
設置的分庫或分表的分片值,經過計算後得到合適的分片數據源或分片表集合,然後直接路由到目標位置,無需再關注SQL本身的條件信息。
/**
* 自定義強制路由分片算法
*
* @author 公衆號:程序員小富
* @date 2024/03/22 11:02
*/
@Slf4j
public class OrderHintCustomAlgorithm implements HintShardingAlgorithm<Long> {
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, HintShardingValue<Long> hintShardingValue) {
/**
* 獲取到設置的分表或者分庫的分片值
* 指定分表時的分片值 hintManager.addTableShardingValue("t_order",2L);
* 指定分庫時的分片值 hintManager.addDatabaseShardingValue("t_order", 100L);
*/
Collection<Long> values = hintShardingValue.getValues();
Collection<String> result = new ArrayList<>();
// 從所有分片表中得到合適的分片表
for (String each : availableTargetNames) {
for (Long value : values) {
Long mod = value % availableTargetNames.size();
if (each.endsWith(String.valueOf(mod))) {
result.add(each);
}
}
}
return result;
}
}
配置算法
配置自定義Hint算法,配置屬性包括strategy
分片策略類型設置成hint
,algorithmClassName
自定義Hint算法的實現類全路徑。使用該算法時無需指定分片健!
spring:
shardingsphere:
# 具體規則配置
rules:
sharding:
# 分片算法定義
sharding-algorithms:
t_order_database_mod:
type: MOD
props:
sharding-count: 2 # 指定分片數量
# 14、自定義 hint 標準算法
t_order_hint_custom_algorithm:
type: CLASS_BASED
props:
# 分片策略
strategy: hint
# 分片算法類
algorithmClassName: com.shardingsphere_101.algorithm.OrderHintCustomAlgorithm
# 自定義屬性
bbbbbb: bbbbbb
tables:
# 邏輯表名稱
t_order:
# 數據節點:數據庫.分片表
actual-data-nodes: db$->{0..1}.t_order_${0..2}
# 分庫策略
database-strategy:
hint:
sharding-algorithm-name: t_order_database_mod
# 分表策略
table-strategy:
hint:
sharding-algorithm-name: t_order_hint_custom_algorithm
測試算法
在執行SQL操作之前,使用 HintManager API
的 addDatabaseShardingValue
和 addTableShardingValue
方法來指定分庫或分表的分片值,這樣算法內通過 HintShardingValue
可以獲取到分片值。注意:如果在執行 SQL 時沒有使用 HintManager 指定分片值,那麼執行SQL將會執行全庫表路由。
@DisplayName("Hint 自動義分片算法-範圍查詢")
@Test
public void queryHintTableTest() {
HintManager hintManager = HintManager.getInstance();
// 指定分表時的分片值
hintManager.addTableShardingValue("t_order",2L);
// 指定分庫時的分片值
hintManager.addDatabaseShardingValue("t_order", 100L);
QueryWrapper<OrderPo> queryWrapper = new QueryWrapper<OrderPo>()
.eq("user_id", 20).eq("order_id", 10);
List<OrderPo> orderPos = orderMapper.selectList(queryWrapper);
log.info("查詢結果:{}", JSON.toJSONString(orderPos));
}
到這關於 shardingsphere-jdbc 的 3種自定義分片算法實現就全部結束了。
總結
本文介紹了 STANDARD、COMPLEX 和 HINT 三種自定義分片算法的實現,和使用過程中一些要注意的事項。ShardingSphere 內置的十幾種算法,其實已經可以滿足我們絕大部分的業務場景,不過,如果考慮到後續的性能優化和擴展性,定製分片算法是個不錯的選擇。
全部demo案例 GitHub 地址:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/shardingsphere101/shardingsphere-algorithms
我是小富~ 下期見