1. Sharding-Jdbc概念與使用技巧
此講解版本為
4.0.0-RC1
,目前最新的版本 2019年5月21日發布
1.1. 綁定表
- 指分片規則一致的主表和子表。例如:
t_order
表和t_order_item
表,均按照order_id
分片,則此兩張表互為綁定表關系。綁定表之間的多表關聯查詢不會出現笛卡爾積關聯,關聯查詢效率將大大提升。舉例說明,如果SQL為:
SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
- 在不配置綁定表關系時,假設分片鍵
order_id
將數值10路由至第0片,將數值11路由至第1片,那么路由后的SQL應該為4條,它們呈現為笛卡爾積:
SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
- 在配置綁定表關系后,路由的SQL應該為2條:、
SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
- 其中
t_order
在FROM的最左側,ShardingSphere將會以它作為整個綁定表的主表。 所有路由計算將會只使用主表的策略,那么t_order_item
表的分片計算將會使用t_order
的條件。故綁定表之間的分區鍵要完全相同。
1.2. 分片算法
通過分片算法將數據分片,支持通過=、BETWEEN和IN分片。分片算法需要應用方開發者自行實現,可實現的靈活度非常高。
目前提供4種分片算法。由於分片算法和業務實現緊密相關,因此並未提供內置分片算法,而是通過分片策略將各種場景提煉出來,提供更高層級的抽象,並提供接口讓應用開發者自行實現分片算法。
- 精確分片算法
對應PreciseShardingAlgorithm,用於處理使用單一鍵作為分片鍵的=與IN進行分片的場景。需要配合StandardShardingStrategy
使用。
- 范圍分片算法
對應RangeShardingAlgorithm,用於處理使用單一鍵作為分片鍵的BETWEEN AND進行分片的場景。需要配合StandardShardingStrategy
使用。
- 復合分片算法
對應ComplexKeysShardingAlgorithm,用於處理使用多鍵作為分片鍵進行分片的場景,包含多個分片鍵的邏輯較復雜,需要應用開發者自行處理其中的復雜度。需要配合ComplexShardingStrategy
使用。
- Hint分片算法
對應HintShardingAlgorithm,用於處理使用Hint行分片的場景。需要配合HintShardingStrategy
使用。
1.3. 分片策略
包含分片鍵和分片算法,由於分片算法的獨立性,將其獨立抽離。真正可用於分片操作的是分片鍵 + 分片算法,也就是分片策略。目前提供5種分片策略。
- 標准分片策略
- 復合分片策略
- 行表達式分片策略
對於簡單的分片算法,可以通過簡單的配置使用,從而避免繁瑣的Java代碼開發,如: t_user_$->{u_id % 8} 表示t_user表根據u_id模8,而分成8張表,表名稱為t_user_0到t_user_7。
- Hint分片策略
- 不分片策略
1.4. SQL Hint
對於分片字段非SQL決定,而由其他外置條件決定的場景,可使用SQL Hint靈活的注入分片字段。例:內部系統,按照員工登錄主鍵分庫,而數據庫中並無此字段。SQL Hint支持通過Java API和SQL注釋(待實現)兩種方式使用。
1.5. SQL支持與不支持
- 看這里
1.6. 行表達式
- ${begin..end}表示范圍區間
- ${[unit1, unit2, unit_x]}表示枚舉值
- 行表達式中如果出現連續多個${ expression }或$->{ expression }表達式,整個表達式最終的結果將會根據每個子表達式的結果進行笛卡爾組合。
${['online', 'offline']}_table${1..3}
最終解析為
online_table1, online_table2, online_table3, offline_table1, offline_table2, offline_table3
1.7. 強制分片路由
通過解析SQL語句提取分片鍵列與值並進行分片是ShardingSphere對SQL零侵入的實現方式。若SQL語句中沒有分片條件,則無法進行分片,需要全路由。
在一些應用場景中,分片條件並不存在於SQL,而存在於外部業務邏輯。因此需要提供一種通過外部指定分片結果的方式,在ShardingSphere中叫做Hint
。
ShardingSphere使用ThreadLocal管理分片鍵值。可以通過編程的方式向HintManager中添加分片條件,該分片條件僅在當前線程內生效。
指定了強制分片路由的SQL將會無視原有的分片邏輯,直接路由至指定的真實數據節點。
1.8. 讀寫分離
- 同一線程且同一數據庫連接內,如有寫入操作,以后的讀操作均從主庫讀取,用於保證數據一致性。
1.9. 編排治理
- 提供注冊中心、配置動態化、數據庫熔斷禁用、調用鏈路等治理能力。
1.10. 注意事項
- 分頁偏移量過大會使數據庫獲取數據性能低下,原因看這里
1.11. 自定義擴展接口
1.11.1. 分庫分表為例
- 進入入口函數,通過springboot配置的入口核心就是右邊的
dataSource
方法,它會把shardingProperties
配置進去,而shardingProperties
的來源就是application.propertes
中的配置屬性 - 通過自定義算法進行配置如下
####################################
# 分庫分表配置
####################################
#actual-data-nodes:真實數據節點,由數據源名 + 表名組成,以小數點分隔。多個表以逗號分隔,支持inline表達式
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds${0..1}.t_order_${0..1}
# 自定義分庫分表算法
spring.shardingsphere.sharding.tables.t_order.databaseStrategy.complex.shardingColumns=order_id,user_id
spring.shardingsphere.sharding.tables.t_order.databaseStrategy.complex.algorithmClassName=com.xxx.shardingjdbc\
.cusalgo.algorithm.DbShardingAlgorithm
## 自定義分表算法
spring.shardingsphere.sharding.tables.t_order.tableStrategy.complex.shardingColumns=order_id,user_id
spring.shardingsphere.sharding.tables.t_order.tableStrategy.complex.algorithmClassName=com.xxx\
.shardingjdbc.cusalgo.algorithm.TableShardingAlgorithm
-
找到配合對應的類如下
-
你可以看到除了tables,你還可以配置很多其他屬性,
bindingTables
,broadcastTables
等等,看名字也知道是綁定表和廣播表,綁定表我第一章就講到了,廣播表理解也很簡單,默認你不分庫的就是廣播表,也就是數據在所有分庫分表的節點都保存一份 -
這里着重講自定義配置類,上面配置文件配置了
DbShardingAlgorithm
這個類就是自定義類,它實現了ComplexKeysShardingAlgorithm
public class DbShardingAlgorithm implements ComplexKeysShardingAlgorithm {
private static Logger logger = LoggerFactory.getLogger(DbShardingAlgorithm.class);
// 取模因子
public static final Integer MODE_FACTOR = 1331;
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, Collection<ShardingValue> shardingValues) {
List<String> shardingResults = new ArrayList<>();
Long shardingIndex = getIndex(shardingValues) % availableTargetNames.size();
// loop and match datasource
for (String name : availableTargetNames) {
// get logic datasource index suffix
String nameSuffix = name.substring(2);
if (nameSuffix.equals(shardingIndex.toString())) {
shardingResults.add(name);
break;
}
}
logger.info("DataSource sharding index : {}", shardingIndex);
return shardingResults;
}
/**
* get datasource sharding index <p>
* sharding algorithm : shardingIndex = (orderId + userId.hashCode()) % db.size
* @param shardingValues
* @return
*/
private long getIndex(Collection<ShardingValue> shardingValues)
{
long shardingIndex = 0L;
ListShardingValue<Long> listShardingValue;
List<Long> shardingValue;
for (ShardingValue sVal : shardingValues) {
listShardingValue = (ListShardingValue<Long>) sVal;
if ("order_id".equals(listShardingValue.getColumnName())) {
shardingValue = (List<Long>) listShardingValue.getValues();
shardingIndex += Math.abs(shardingValue.get(0)) % MODE_FACTOR;
} else if ("user_id".equals(listShardingValue.getColumnName())) {
shardingValue = (List<Long>) listShardingValue.getValues();
// 這里 % 1313 僅僅只是防止溢出
shardingIndex += Math.abs(shardingValue.get(0).hashCode()) % MODE_FACTOR;
}
}
return shardingIndex;
}
}
繼續追蹤進入
可以發現它總共實現了5個接口配置,上面的ComplexKeysShardingAlgorithm
就來自complex
的配置
- 至於該實現哪些接口,看下圖
- 上述四個接口,就是我們用戶可以自定義實現的接口了,寫好實現類把全類名配置上去就可以用了
想要全面了解Sharding-jdbc和它相關組件的,移步這里