一口氣搞懂分庫分表 12 種分片算法,大廠都在用
前言
本文是《ShardingSphere5.x分庫分表原理與實戰(zhàn)》系列的第五篇文章,我們一起梳理下ShardingSphere框架中的核心部分分片策略和分片算法,其內(nèi)部針為我們提供了多種分片策略和分片算法,來應(yīng)對不同的業(yè)務(wù)場景,本著拿來即用的原則。
這次將詳細(xì)介紹如何在ShardingSphere-jdbc中實戰(zhàn) 5 種分片策略和 12 種分片算法,自定義分片算法,比較它們的應(yīng)用場景以及優(yōu)劣。
圖片
全部demo案例 GitHub 地址:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/shardingsphere101/shardingsphere-algorithms
分片策略
分片策略是分片鍵和分片算法的組合策略,真正用于實現(xiàn)數(shù)據(jù)分片操作的是分片鍵與相應(yīng)的分片算法。在分片策略中,分片鍵確定了數(shù)據(jù)的拆分依據(jù),分片算法則決定了如何對分片鍵值運算,將數(shù)據(jù)路由到哪個物理分片中。
由于分片算法的獨立性,使得分片策略具有更大的靈活性和可擴展性。這意味著可以根據(jù)具體需求選擇不同的分片算法,或者開發(fā)自定義的分片算法,以適應(yīng)各種不同的分片場景。在分表和分庫時使用分片策略和分片算法的方式是一致的。
注意:如果在某種分片策略中使用了不受支持的SQL操作符,比如 MYSQL 某些函數(shù)等,那么系統(tǒng)將無視分片策略,進行全庫表路由操作。這個在使用時要慎重!
ShardingSphere對外提供了standard、complex、hint、inline、none5種分片策略。不同的分片策略可以搭配使用不同的分片算法,這樣可以靈活的應(yīng)對復(fù)雜業(yè)務(wù)場景。
標(biāo)準(zhǔn)分片策略
標(biāo)準(zhǔn)分片策略(standard)適用于具有單一分片鍵的標(biāo)準(zhǔn)分片場景。該策略支持精確分片,即在SQL中包含=、in操作符,以及范圍分片,包括BETWEEN AND、>、<、>=、<=等范圍操作符。
該策略下有兩個屬性,分片字段shardingColumn和分片算法名shardingAlgorithmName。
spring:
shardingsphere:
rules:
sharding:
tables:
t_order: # 邏輯表名稱
# 數(shù)據(jù)節(jié)點:數(shù)據(jù)庫.分片表
actual-data-nodes: db$->{0..1}.t_order_${1..10}
# 分庫策略
databaseStrategy: # 分庫策略
standard: # 用于單分片鍵的標(biāo)準(zhǔn)分片場景
shardingColumn: order_id # 分片列名稱
shardingAlgorithmName: # 分片算法名稱
tableStrategy: # 分表策略,同分庫策略
行表達(dá)式分片策略
行表達(dá)式分片策略(inline)適用于具有單一分片鍵的簡單分片場景,支持SQL語句中=和in操作符。
它的配置相當(dāng)簡潔,該分片策略支持在配置屬性algorithm-expression中書寫Groovy表達(dá)式,用來定義對分片健的運算邏輯,無需單獨定義分片算法了。
spring:
shardingsphere:
rules:
sharding:
tables:
t_order: # 邏輯表名稱
# 數(shù)據(jù)節(jié)點:數(shù)據(jù)庫.分片表
actual-data-nodes: db$->{0..1}.t_order_${1..10}
# 分庫策略
databaseStrategy: # 分庫策略
inline: # 行表達(dá)式類型分片策略
algorithm-expression: db$->{order_id % 2} Groovy表達(dá)式
tableStrategy: # 分表策略,同分庫策略
復(fù)合分片策略
復(fù)合分片策略(complex)適用于多個分片鍵的復(fù)雜分片場景,屬性shardingColumns中多個分片健以逗號分隔。支持 SQL 語句中有>、>=、<=、<、=、IN 和 BETWEEN AND 等操作符。
比如:我們希望通過user_id和order_id等多個字段共同運算得出數(shù)據(jù)路由到具體哪個分片中,就可以應(yīng)用該策略。
spring:
shardingsphere:
rules:
sharding:
tables:
t_order: # 邏輯表名稱
# 數(shù)據(jù)節(jié)點:數(shù)據(jù)庫.分片表
actual-data-nodes: db$->{0..1}.t_order_${1..10}
# 分庫策略
databaseStrategy: # 分庫策略
complex: # 用于多分片鍵的復(fù)合分片場景
shardingColumns: order_id,user_id # 分片列名稱,多個列以逗號分隔
shardingAlgorithmName: # 分片算法名稱
tableStrategy: # 分表策略,同分庫策略
Hint分片策略
Hint強制分片策略相比于其他幾種分片策略稍有不同,該策略無需配置分片健,由外部指定分庫和分表的信息,可以讓SQL在指定的分庫、分表中執(zhí)行。
使用場景:
- 分片字段不存在SQL和數(shù)據(jù)庫表結(jié)構(gòu)中,而存在于外部業(yè)務(wù)邏輯。
- 強制在指定數(shù)據(jù)庫進行某些數(shù)據(jù)操作。
比如,我們希望用user_id做分片健進行路由訂單數(shù)據(jù),但是t_order表中也沒user_id這個字段啊,這時可以通過Hint API手動指定分片庫、表等信息,強制讓數(shù)據(jù)插入指定的位置。
spring:
shardingsphere:
rules:
sharding:
tables:
t_order: # 邏輯表名稱
# 數(shù)據(jù)節(jié)點:數(shù)據(jù)庫.分片表
actual-data-nodes: db$->{0..1}.t_order_${1..10}
# 分庫策略
databaseStrategy: # 分庫策略
hint: # Hint 分片策略
shardingAlgorithmName: # 分片算法名稱
tableStrategy: # 分表策略,同分庫策略
不分片策略
不分片策略比較好理解,設(shè)置了不分片策略,那么對邏輯表的所有操作將會執(zhí)行全庫表路由。
spring:
shardingsphere:
rules:
sharding:
tables:
t_order: # 邏輯表名稱
# 數(shù)據(jù)節(jié)點:數(shù)據(jù)庫.分片表
actual-data-nodes: db$->{0..1}.t_order_${1..10}
# 分庫策略
databaseStrategy: # 分庫策略
none: # 不分片
tableStrategy: # 分表策略,同分庫策略
分片算法
ShardingSphere 內(nèi)置了多種分片算法,按照類型可以劃分為自動分片算法、標(biāo)準(zhǔn)分片算法、復(fù)合分片算法和 Hint 分片算法,能夠滿足我們絕大多數(shù)業(yè)務(wù)場景的需求。
此外,考慮到業(yè)務(wù)場景的復(fù)雜性,內(nèi)置算法也提供了自定義分片算法的方式,我們可以通過編寫 Java代碼來完成復(fù)雜的分片邏輯。下邊逐個算法實踐一下,看看每種算法的實際執(zhí)行效果。
開始前,我要吐槽下官方文檔,對于算法這種至關(guān)重要的內(nèi)容,解釋描述的過于潦草,對于新手入門不友好,學(xué)習(xí)成本偏高啊
準(zhǔn)備工作
給邏輯表配置完算法后,先執(zhí)行創(chuàng)建表的SQL,這樣就可以依據(jù)你的算法在db內(nèi)快速生成分片表,所以不要總是問我要表結(jié)構(gòu)了,哈哈哈。如果有不明白的小伙伴可以看我上一篇對于autoTable的介紹 分庫分表如何管理不同實例中幾萬張分片表?。
自動分片算法
1、MOD
取模分片算法是內(nèi)置的一種比較簡單的算法,定義算法時類型MOD,表達(dá)式大致(分片健/數(shù)據(jù)庫實例) % sharding-count,它只有一個 props 屬性sharding-count代表分片表的數(shù)量。
圖片
這個 sharding-count 數(shù)量使用時有點小坑,比如db0和db1都有分片表t_order_1,那么實際上數(shù)量只能算一個。YML核心配置如下:
spring:
shardingsphere:
rules:
sharding:
# 自動分片表規(guī)則配置
auto-tables:
t_order:
actual-data-sources: db$->{0..1}
sharding-strategy:
standard:
sharding-column: order_date
sharding-algorithm-name: t_order_table_mod
# 分片算法定義
sharding-algorithms:
t_order_table_mod:
type: MOD # 取模分片算法
props:
# 指定分片數(shù)量
sharding-count: 6
tables:
t_order: # 邏輯表名稱
actual-data-nodes: db$->{0..1}.t_order_${0..2}
# 分庫策略
database-strategy:
....
# 分表策略
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: t_order_table_mod
2、HASH_MOD
哈希取模分片算法是內(nèi)置取模分片算法的一個升級版本,定義算法時類型HASH_MOD,也只有一個props屬性sharding-count代表分片的數(shù)量。表達(dá)式hash(分片健/數(shù)據(jù)庫實例) % sharding-count。
圖片
YML核心配置如下:
spring:
shardingsphere:
rules:
sharding:
# 自動分片表規(guī)則配置
auto-tables:
t_order:
actual-data-sources: db$->{0..1}
sharding-strategy:
standard:
sharding-column: order_date
sharding-algorithm-name: t_order_table_hash_mod
# 分片算法定義
sharding-algorithms:
t_order_table_hash_mod:
type: HASH_MOD # 哈希取模分片算法
props:
# 指定分片數(shù)量
sharding-count: 6
tables:
t_order: # 邏輯表名稱
actual-data-nodes: db$->{0..1}.t_order_${0..2}
# 分庫策略
database-strategy:
....
# 分表策略
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: t_order_table_hash_mod
3、VOLUME_RANGE
基于分片容量的范圍分片算法,依據(jù)數(shù)據(jù)容量來均勻分布到分片表中。
它適用于數(shù)據(jù)增長趨勢相對均勻,按分片容量將數(shù)據(jù)均勻地分布到不同的分片表中,可以有效避免數(shù)據(jù)傾斜問題;由于數(shù)據(jù)已經(jīng)被按照范圍進行分片,支持頻繁進行范圍查詢場景。
不僅如此,該算法支持動態(tài)的分片調(diào)整,可以根據(jù)實際業(yè)務(wù)數(shù)據(jù)的變化動態(tài)調(diào)整分片容量和范圍,使得系統(tǒng)具備更好的擴展性和靈活性。
VOLUME_RANGE算法主要有三個屬性:
圖片
看完是不是一臉懵逼,上界下界都是什么含義,我們實際使用一下就清晰了。為t_order邏輯表設(shè)置VOLUME_RANGE分片算法,range-lower下界數(shù)為 2,range-upper上界數(shù)為 20,分量容量sharding-volume 10。
yml核心配置如下:
# 分片算法定義
spring:
shardingsphere:
rules:
sharding:
# 自動分片表規(guī)則配置
auto-tables:
t_order:
actual-data-sources: db$->{0..1}
sharding-strategy:
standard:
sharding-column: order_date
sharding-algorithm-name: t_order_table_volume_range
sharding-algorithms:
t_order_table_volume_range:
type: VOLUME_RANGE
props:
range-lower: 2 # 范圍下界,超過邊界的數(shù)據(jù)會報錯
range-upper: 20 # 范圍上界,超過邊界的數(shù)據(jù)會報錯
sharding-volume: 10 # 分片容量
tables:
t_order: # 邏輯表名稱
actual-data-nodes: db$->{0..1}.t_order_${0..2}
# 分庫策略
database-strategy:
....
# 分表策略
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: t_order_table_volume_range
這個配置的意思就是說,分片健t_order_id的值在界值 [range-lower,range-upper) 范圍內(nèi),每個分片表最大存儲 10 條數(shù)據(jù);低于下界的值 [ 1,2 ) 數(shù)據(jù)分布到 t_order_0,在界值范圍內(nèi)的數(shù)據(jù) [ 2,20 ) 遵循每滿足 10 條依次放入 t_order_1、t_order_2;超出上界的數(shù)據(jù)[ 20,∞ ) 即便前邊的分片表里未滿 10條剩下的也全部放在 t_order_3。
那么它的數(shù)據(jù)分布應(yīng)該如下:
- [ 0,2 )數(shù)據(jù)分布到 t_order_0
- [ 2,12 )數(shù)據(jù)分布到 t_order_1
- [ 12,20 )數(shù)據(jù)分布到 t_order_2
- [ 20,∞ )數(shù)據(jù)分布到 t_order_3
基于分片容量的范圍分片算法
接著準(zhǔn)備插入40條數(shù)據(jù),其中分片健字段t_order_id值從1~40,我們看到實際插入庫的數(shù)據(jù)和上邊配置的規(guī)則是一致的。超出range-lower、range-upper邊界的部分?jǐn)?shù)據(jù),比如:t_order_2表未滿 10條也不再插入,全部放入了t_order_3分片表中。
圖片
4、BOUNDARY_RANGE
基于分片邊界的范圍分片算法,和分片容量算法不同,這個算法根據(jù)數(shù)據(jù)的取值范圍進行分片,特別適合按數(shù)值范圍頻繁查詢的場景。該算法只有一個屬性sharding-ranges為分片健值的范圍區(qū)間。
圖片
比如,我們配置sharding-ranges=10,20,30,40,它的范圍默認(rèn)是從 0開始,范圍區(qū)間前閉后開。配置算法以后執(zhí)行建表語句,生成數(shù)據(jù)節(jié)點分布如:
db0-
|_t_order_0
|_t_order_2
|_t_order_4
db1-
|_t_order_1
|_t_order_3
那么它的數(shù)據(jù)分布應(yīng)該如下:
[ 0,10 )數(shù)據(jù)分布到t_order_0,
[ 10,20 )數(shù)據(jù)分布到t_order_1,
[ 20,30 )數(shù)據(jù)分布到t_order_2,
[ 30,40 )數(shù)據(jù)分布到t_order_3,
[ 40,∞ )數(shù)據(jù)分布到t_order_4。
基于分片邊界的范圍分片算法
BOUNDARY_RANGE算法的YML核心配置如下:
spring:
shardingsphere:
rules:
sharding:
# 自動分片表規(guī)則配置
auto-tables:
t_order:
actual-data-sources: db$->{0..1}
sharding-strategy:
standard:
sharding-column: order_date
sharding-algorithm-name: t_order_table_boundary_range
sharding-algorithms:
# 基于分片邊界的范圍分片算法
t_order_table_boundary_range:
type: BOUNDARY_RANGE
props:
sharding-ranges: 10,20,30,40 # 分片的范圍邊界,多個范圍邊界以逗號分隔
tables:
t_order: # 邏輯表名稱
actual-data-nodes: db$->{0..1}.t_order_${0..2}
# 分庫策略
database-strategy:
....
# 分表策略
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: t_order_table_boundary_range
也插入40條數(shù)據(jù),其中分片健字段t_order_id值從1~40,和上邊分析的數(shù)據(jù)分布結(jié)果大致相同。看到第一張分片表中 t_order_0 只有 9 條數(shù)據(jù),這是因為咱們插入數(shù)據(jù)的分片健值是從 1 開始,但算法是從 0 開始計算。
圖片
5、AUTO_INTERVAL
自動時間段分片算法,適用于以時間字段作為分片健的分片場景,和VOLUME_RANGE基于容量的分片算法用法有點類似,不同的是AUTO_INTERVAL依據(jù)時間段進行分片。主要有三個屬性datetime-lower分片健值開始時間(下界)、datetime-upper分片健值結(jié)束時間(上界)、sharding-seconds單一分片表所能容納的時間段。
圖片
這里分片健已經(jīng)從t_order_id替換成了order_date?,F(xiàn)在屬性 datetime-lower 設(shè)為 2023-01-01 00:00:00,datetime-upper 設(shè)為 2025-01-01 00:00:00,sharding-seconds為 31536000 秒(一年)。策略配置上有些改動,將分庫和分表的算法全替換成AUTO_INTERVAL。
YML核心配置如下:
spring:
shardingsphere:
rules:
sharding:
# 自動分片表規(guī)則配置
auto-tables:
t_order:
actual-data-sources: db$->{0..1}
sharding-strategy:
standard:
sharding-column: order_date
sharding-algorithm-name: t_order_table_auto_interval
# 分片算法定義
sharding-algorithms:
# 自動時間段分片算法
t_order_table_auto_interval:
type: AUTO_INTERVAL
props:
datetime-lower: '2023-01-01 00:00:00' # 分片的起始時間范圍,時間戳格式:yyyy-MM-dd HH:mm:ss
datetime-upper: '2025-01-01 00:00:00' # 分片的結(jié)束時間范圍,時間戳格式:yyyy-MM-dd HH:mm:ss
sharding-seconds: 31536000 # 單一分片所能承載的最大時間,單位:秒,允許分片鍵的時間戳格式的秒帶有時間精度,但秒后的時間精度會被自動抹去
tables:
# 邏輯表名稱
t_order:
# 數(shù)據(jù)節(jié)點:數(shù)據(jù)庫.分片表
actual-data-nodes: db$->{0..1}.t_order_${0..2}
# 分庫策略
database-strategy:
standard:
sharding-column: order_date
sharding-algorithm-name: t_order_table_auto_interval
# 分表策略
# table-strategy:
# standard:
# sharding-column: order_date
# sharding-algorithm-name: t_order_table_auto_interval
只要你理解了上邊 VOLUME_RANGE 算法的數(shù)據(jù)分布規(guī)則,那么這個算法也很容易明白,分片健值在界值范圍內(nèi) [datetime-lower,datetime-upper) 遵循每滿足 sharding-seconds 時間段的數(shù)據(jù)放入對應(yīng)分片表,超出界值的數(shù)據(jù)上下順延到其他分片中。
它的數(shù)據(jù)分布應(yīng)該如下:
- [ 2023-01-01 00:00:00,2024-01-01 00:00:00 )數(shù)據(jù)分布到 t_order_0,
- [ 2024-01-01 00:00:00,2025-01-01 00:00:00 )數(shù)據(jù)分布到 t_order_1,
- [ 2025-01-01 00:00:00,2026-01-01 00:00:00 )數(shù)據(jù)分布到 t_order_2。
- [ 2026-01-01 00:00:00,∞ )數(shù)據(jù)分布到 t_order_3。
為了方便測試,手動執(zhí)行插入不同日期的數(shù)據(jù),按照上邊配置的規(guī)則應(yīng)該t_order_0會有一條 23 年的數(shù)據(jù),t_order_1 中有兩條 24 年的數(shù)據(jù),t_order_2 中有兩條 25 年的數(shù)據(jù),t_order_3 中有兩條 26、27 年的數(shù)據(jù)。
// 放入 t_order_0 分片表
INSERT INTO `t_order` VALUES (1, '2023-03-20 00:00:00', 1, '1', 1, 1.00);
// 放入 t_order_1 分片表
INSERT INTO `t_order` VALUES (2, '2024-03-20 00:00:00', 2, '2', 2,1.00);
INSERT INTO `t_order` VALUES (3, '2024-03-20 00:00:00', 3, '3', 3, 1.00);
// 放入 t_order_2 分片表
INSERT INTO `t_order` VALUES (4,'2025-03-20 00:00:00',4, '4', 4, 1.00);
INSERT INTO `t_order` VALUES (5,'2025-03-20 00:00:00',5, '5', 5, 1.00);
// 放入 t_order_3 分片表
INSERT INTO `t_order` VALUES (6,'2026-03-20 00:00:00',6, '6', 6, 1.00);
INSERT INTO `t_order` VALUES (7,'2027-03-20 11:19:58',7, '7', 7, 1.00);
查看實際的數(shù)據(jù)分布情況和預(yù)想的結(jié)果完全一致,至此內(nèi)置算法全部使用大成。
標(biāo)準(zhǔn)分片算法
6、INLINE
行表達(dá)式分片算法,適用于比較簡單的分片場景,利用Groovy表達(dá)式在算法屬性內(nèi),直接書寫分片邏輯,省卻了配置和代碼開發(fā),只支持SQL語句中的 = 和 IN 的分片操作,只支持單分片鍵。
圖片
該算法有兩屬性:
- algorithm-expression:編寫Groovy的表達(dá)式,比如: t_order_$->{t_order_id % 3} 表示根據(jù)分片健 t_order_id 取模獲得 3 張 t_order 分片表 t_order_0 到 t_order_2。
- allow-range-query-with-inline-sharding:由于該算法只支持含有 = 和 IN 操作符的SQL,一旦SQL使用了范圍查詢 >、< 等操作會報錯。要想執(zhí)行范圍查詢成功,該屬性開啟為true即可,一旦開啟范圍查詢會無視分片策略,進行全庫表路由查詢,這個要慎重開啟!
YML核心配置如下:
spring:
shardingsphere:
# 具體規(guī)則配置
rules:
sharding:
# 分片算法定義
sharding-algorithms:
# 標(biāo)準(zhǔn)分片算法
# 行表達(dá)式分片算法
t_order_table_inline:
type: INLINE
props:
algorithm-expression: t_order_$->{order_id % 3} # 分片算法的行表達(dá)式
allow-range-query-with-inline-sharding: false # 是否允許范圍查詢。注意:范圍查詢會無視分片策略,進行全路由,默認(rèn) false
tables:
# 邏輯表名稱
t_order:
# 數(shù)據(jù)節(jié)點:數(shù)據(jù)庫.分片表
actual-data-nodes: db$->{0..1}.t_order_${0..2}
# 分庫策略
database-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: t_order_database_algorithms
# 分表策略
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: t_order_table_inline
7、INTERVAL
時間范圍分片算法,針對于時間字段(字符串類型)作為分片健的范圍分片算法,適用于按照天、月、年這種固定區(qū)間的數(shù)據(jù)分片。 上邊使用其它時間分片算法時,用的都是t_order_n后綴編號格式的分片表。但業(yè)務(wù)上往往需要的可能是按月、年t_order_yyyyMM的這種分片表格式。
時間范圍分片算法(INTERVAL),可以輕松實現(xiàn)這種場景,它的屬性比較多,逐個解釋下:
圖片
- datetime-pattern:分片健值的時間格式,必須是Java DateTimeFormatter類支持的轉(zhuǎn)換類型
- datetime-lower:分片健值的下界,超過會報錯,格式必須與datetime-pattern一致
- datetime-upper:分片健值的上界,超過會報錯,格式必須與datetime-pattern一致
- sharding-suffix-pattern:分片表后綴名格式,yyyyMM、yyyyMMdd等格式,分片表格式的定義要結(jié)合datetime-interval-unit的單位,比如:t_order_yyyyMM格式表示分片表存的月的數(shù)據(jù),t_order_yyyy格式表示分片表存的年的數(shù)據(jù);
- datetime-interval-unit:分片間隔單位,超過該時間間隔將進入下一分片。它遵循 Java ChronoUnit 枚舉,比如:MONTHS、DAYS等;
- datetime-interval-amount:分片間隔數(shù),和datetime-interval-unit是緊密配合使用;
接下來實現(xiàn)個按月存儲數(shù)據(jù)的場景,用t_order_202401~t_order_202406 6張分片表存儲前半年的數(shù)據(jù),每張分片表存儲一個月的數(shù)據(jù)。interval_value字段作為分片健,時間字符串類型,允許的分片值時間范圍 2024-01-01 00:00:00~2024-06-30 23:59:59 不在范圍內(nèi)插入報錯。
spring:
shardingsphere:
rules:
sharding:
# 分片算法定義
sharding-algorithms:
t_order_database_mod:
type: MOD
props:
sharding-count: 2 # 指定分片數(shù)量
t_order_table_interval:
type: INTERVAL
props:
datetime-pattern: "yyyy-MM-dd HH:mm:ss" # 分片字段格式
datetime-lower: "2024-01-01 00:00:00" # 范圍下限
datetime-upper: "2024-06-30 23:59:59" # 范圍上限
sharding-suffix-pattern: "yyyyMM" # 分片名后綴,可以是MM,yyyyMMdd等。
datetime-interval-amount: 1 # 分片間隔,這里指一個月
datetime-interval-unit: "MONTHS" # 分片間隔單位
tables:
# 邏輯表名稱
t_order:
# 數(shù)據(jù)節(jié)點:數(shù)據(jù)庫.分片表
actual-data-nodes: db$->{0..1}.t_order_${202401..202406}
# 分庫策略
database-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: t_order_database_mod
# 分表策略
table-strategy:
standard:
sharding-column: interval_value
sharding-algorithm-name: t_order_table_interval
keyGenerateStrategy:
column: id
keyGeneratorName: t_order_snowflake
配置完成后插入測試數(shù)據(jù) 1月~7月,正常情況下前 6 個月的數(shù)據(jù)會正常插入,超過界值的 7月數(shù)據(jù)應(yīng)該會報錯。
// 放入 t_order_202401 分片表
INSERT INTO `t_order` VALUES (1, 1, '1', 1, 1.00, '2024-01-01 00:00:00', 1);
// 放入 t_order_202402 分片表
INSERT INTO `t_order` VALUES (2, 2, '2', 2, 1.00, '2024-02-01 00:00:00', 1);
// 放入 t_order_202403 分片表
INSERT INTO `t_order` VALUES (3, 3, '3', 3, 1.00, '2024-03-01 00:00:00', 1);
// 放入 t_order_202404 分片表
INSERT INTO `t_order` VALUES (4, 4, '4', 4, 1.00, '2024-04-01 00:00:00', 1);
// 放入 t_order_202405 分片表
INSERT INTO `t_order` VALUES (5, 5, '5', 5, 1.00, '2024-05-01 00:00:00', 1);
// 放入 t_order_202406 分片表
INSERT INTO `t_order` VALUES (6, 6, '6', 6, 1.00, '2024-06-01 00:00:00', 1);
// 插入會報錯
INSERT INTO `t_order` VALUES (7, 7, '7', 7, 1.00, '2024-07-01 00:00:00', 1);
看到實際的入庫的效果和預(yù)期的一致,一月的數(shù)據(jù)存到t_order_202401,二月的數(shù)據(jù)存到t_order_202402~,在插入 7月數(shù)據(jù)的時候報錯了。
圖片
COSID 類型算法
ShardingSphere 提供了三種基于散列散列算法的CosId(它是一款性能極高分布式ID生成器)分片算法,這個算法的核心思想是通過散列算法對CosId生成的分布式ID和分片鍵值進行處理,以確定數(shù)據(jù)應(yīng)該存放在哪個具體的數(shù)據(jù)節(jié)點上。
使用散列算法的優(yōu)勢,可以將數(shù)據(jù)按照一定規(guī)則映射到不同的數(shù)據(jù)節(jié)點上,能夠確保數(shù)據(jù)的均勻分布,避免某些節(jié)點負(fù)載過重或者數(shù)據(jù)傾斜的情況。
三個算法與其他分片算法主要區(qū)別在于底層的實現(xiàn),在配置上使用上基本沒太多區(qū)別。
8、COSID_MOD
基于 CosId 的取模分片算法和普通的MOD算法使用上略有不同,mod為分片數(shù)量,logic-name-prefix分片數(shù)據(jù)源或真實表的前綴格式。
圖片
yml核心配置如下:
spring:
shardingsphere:
rules:
sharding:
# 分片算法定義
sharding-algorithms:
t_order_database_mod:
type: MOD
props:
sharding-count: 2 # 指定分片數(shù)量
# 8、基于 CosId 的取模分片算法
t_order_table_cosid_mod:
type: COSID_MOD
props:
mod: 3 # 分片數(shù)量
logic-name-prefix: t_order_ # 分片數(shù)據(jù)源或真實表的前綴格式
tables:
# 邏輯表名稱
t_order:
# 數(shù)據(jù)節(jié)點:數(shù)據(jù)庫.分片表
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_table_cosid_mod
keyGenerateStrategy:
column: id
keyGeneratorName: t_order_snowflake
9、COSID_INTERVAL
基于 CosId 的固定時間范圍的分片算法,和INTERVAL算法的用法很相似,不同點在于增加了zone-id時區(qū)屬性,logic-name-prefix分片數(shù)據(jù)源或真實表的前綴格式,上下界datetime-lower、datetime-upper范圍的時間格式是固定的yyyy-MM-dd HH:mm:ss。
圖片
yml核心配置如下:
spring:
shardingsphere:
rules:
sharding:
# 分片算法定義
sharding-algorithms:
t_order_database_mod:
type: MOD
props:
sharding-count: 2 # 指定分片數(shù)量
# 基于 CosId 的固定時間范圍的分片算法
t_order_table_cosid_interval:
type: COSID_INTERVAL
props:
zone-id: "Asia/Shanghai" # 時區(qū),必須遵循 java.time.ZoneId 的所含值。 例如:Asia/Shanghai
logic-name-prefix: t_order_ # 分片數(shù)據(jù)源或真實表的前綴格式
sharding-suffix-pattern: "yyyyMM" # 分片數(shù)據(jù)源或真實表的后綴格式,必須遵循 Java DateTimeFormatter 的格式,必須和 datetime-interval-unit 保持一致。例如:yyyyMM
datetime-lower: "2024-01-01 00:00:00" # 時間分片下界值,格式與 yyyy-MM-dd HH:mm:ss 的時間戳格式一致
datetime-upper: "2024-12-31 00:00:00" # 時間分片上界值,格式與 yyyy-MM-dd HH:mm:ss 的時間戳格式一致
datetime-interval-unit: "MONTHS" # 分片鍵時間間隔單位,必須遵循 Java ChronoUnit 的枚舉值。例如:MONTHS
datetime-interval-amount: 1 # 分片鍵時間間隔,超過該時間間隔將進入下一分片
tables:
# 邏輯表名稱
t_order:
# 數(shù)據(jù)節(jié)點:數(shù)據(jù)庫.分片表
actual-data-nodes: db$->{0..1}.t_order_${202401..202412}
# 分庫策略
database-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: t_order_database_mod
# 分表策略
table-strategy:
standard:
sharding-column: interval_value
sharding-algorithm-name: t_order_table_cosid_interval
keyGenerateStrategy:
column: id
keyGeneratorName: t_order_snowflake
10、COSID_INTERVAL_SNOWFLAKE
基于 CosId 的雪花ID固定時間范圍的分片算法,和上邊的COSID_INTERVAL算法不同之處在于,底層用于散列的COSID的生成方式是基于雪花算法(Snowflake),內(nèi)部結(jié)合了時間戳、節(jié)點標(biāo)識符和序列號等,這樣有助于數(shù)據(jù)分布更均勻些。
圖片
使用除了type類型不同COSID_INTERVAL_SNOWFLAKE外,其他屬性用法和COSID_INTERVAL完全一致。yml核心配置如下:
spring:
shardingsphere:
rules:
sharding:
# 分片算法定義
sharding-algorithms:
t_order_database_mod:
type: MOD
props:
sharding-count: 2 # 指定分片數(shù)量
# 基于 CosId 的固定時間范圍的分片算法
t_order_table_cosid_interval_snowflake:
type: COSID_INTERVAL_SNOWFLAKE
props:
zone-id: "Asia/Shanghai" # 時區(qū),必須遵循 java.time.ZoneId 的所含值。 例如:Asia/Shanghai
logic-name-prefix: t_order_ # 分片數(shù)據(jù)源或真實表的前綴格式
sharding-suffix-pattern: "yyyyMM" # 分片數(shù)據(jù)源或真實表的后綴格式,必須遵循 Java DateTimeFormatter 的格式,必須和 datetime-interval-unit 保持一致。例如:yyyyMM
datetime-lower: "2024-01-01 00:00:00" # 時間分片下界值,格式與 yyyy-MM-dd HH:mm:ss 的時間戳格式一致
datetime-upper: "2024-12-31 00:00:00" # 時間分片上界值,格式與 yyyy-MM-dd HH:mm:ss 的時間戳格式一致
datetime-interval-unit: "MONTHS" # 分片鍵時間間隔單位,必須遵循 Java ChronoUnit 的枚舉值。例如:MONTHS
datetime-interval-amount: 1 # 分片鍵時間間隔,超過該時間間隔將進入下一分片
tables:
# 邏輯表名稱
t_order:
# 數(shù)據(jù)節(jié)點:數(shù)據(jù)庫.分片表
actual-data-nodes: db$->{0..1}.t_order_${202401..202412}
# 分庫策略
database-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: t_order_database_mod
# 分表策略
table-strategy:
standard:
sharding-column: interval_value
sharding-algorithm-name: t_order_table_cosid_interval_snowflake
keyGenerateStrategy:
column: id
keyGeneratorName: t_order_snowflake
復(fù)合分片算法
11、COMPLEX_INLINE
復(fù)合行表達(dá)式分片算法,適用于多分片健的簡單分片場景,和行表達(dá)式分片算法使用的方式基本一樣。多了一個屬性sharding-columns分片列名稱,多個列用逗號分隔。特別注意:使用多分片鍵復(fù)合算法,一定要基于復(fù)合分片策略進行設(shè)置。
圖片
我們對現(xiàn)有的分庫分表算法進行了改進,將分片策略修改為complex,sharding-columns單個分片鍵升級為多個分片鍵逗號分隔。例如,將分庫表達(dá)式從db$->{order_id % 2}調(diào)整為db$->{(order_id + user_id) % 2},就實現(xiàn)了多個分片鍵的應(yīng)用。
yml核心的配置如下:
spring:
shardingsphere:
# 具體規(guī)則配置
rules:
sharding:
# 分片算法定義
sharding-algorithms:
t_order_database_complex_inline_algorithms:
type: COMPLEX_INLINE
props:
sharding-columns: order_id, user_id # 分片列名稱,多個列用逗號分隔。
algorithm-expression: db$->{(order_id + user_id) % 2} # 分片算法的行表達(dá)式
allow-range-query-with-inline-sharding: false # 是否允許范圍查詢。注意:范圍查詢會無視分片策略,進行全路由,默認(rèn) false
# 11、復(fù)合行表達(dá)式分片算法
t_order_table_complex_inline:
type: COMPLEX_INLINE
props:
sharding-columns: order_id, user_id # 分片列名稱,多個列用逗號分隔。
algorithm-expression: t_order_$->{ (order_id + user_id) % 3 } # 分片算法的行表達(dá)式
allow-range-query-with-inline-sharding: false # 是否允許范圍查詢。注意:范圍查詢會無視分片策略,進行全路由,默認(rèn) false
tables:
# 邏輯表名稱
t_order:
# 數(shù)據(jù)節(jié)點:數(shù)據(jù)庫.分片表
actual-data-nodes: db$->{0..1}.t_order_${0..2}
# 分庫策略
database-strategy:
complex:
shardingColumns: order_id, user_id
sharding-algorithm-name: t_order_database_complex_inline_algorithms
# 分表策略
table-strategy:
complex:
shardingColumns: order_id, user_id
sharding-algorithm-name: t_order_table_complex_inline
keyGenerateStrategy:
column: id
keyGeneratorName: t_order_snowflake
Hint 分片算法
12、HINT_INLINE
Hint 行表達(dá)式分片算法(強制路由分片算法),允許我們指定數(shù)據(jù)分布的分片庫和分表的位置。這個算法只有一個屬性algorithm-expression,直接利用Groovy表達(dá)式在其中書寫分片邏輯。
如果想要向db0.t_order_1分片表中插入一條數(shù)據(jù),但我的 Insert SQL 中并沒有分片健呀,執(zhí)意執(zhí)行插入操作可能就會導(dǎo)致全庫表路由,插入的數(shù)據(jù)就會重復(fù),顯然是不能接受的。Hint 算法可以很好的解決此場景。
HINT_INLINE 算法一定要在 HINT 分片策略內(nèi)使用,否則會報錯。
核心的配置如下:其中兩個表達(dá)式db$->{Integer.valueOf(value) % 2}和t_order_$->{Integer.valueOf(value) % 3}中的value值分別是我們通過 Hint API 傳入的分庫值和分表值。
spring:
shardingsphere:
rules:
sharding:
# 分片算法定義
sharding-algorithms:
# Hint 行表達(dá)式分片算法
t_order_database_hint_inline:
type: HINT_INLINE
props:
algorithm-expression: db$->{Integer.valueOf(value) % 2} # 分片算法的行表達(dá)式,默認(rèn)值${value}
t_order_table_hint_inline:
type: HINT_INLINE
props:
algorithm-expression: t_order_$->{Integer.valueOf(value) % 3} # 分片算法的行表達(dá)式,默認(rèn)值${value}
tables:
# 邏輯表名稱
t_order:
# 數(shù)據(jù)節(jié)點:數(shù)據(jù)庫.分片表
actual-data-nodes: db$->{0..1}.t_order_${0..2}
# 分庫策略
database-strategy:
hint:
sharding-algorithm-name: t_order_database_hint_inline
# 分表策略
table-strategy:
hint:
sharding-algorithm-name: t_order_table_hint_inline
keyGenerateStrategy:
column: id
keyGeneratorName: t_order_snowflake
配置完分片算法,如何將value值傳遞進來?通過HintManager設(shè)置邏輯表的分庫addDatabaseShardingValue、分表addTableShardingValue,強制數(shù)據(jù)分布到指定位置。
@DisplayName("測試 hint_inline 分片算法插入數(shù)據(jù)")
@Test
public void insertHintInlineTableTest() {
HintManager hintManager = HintManager.getInstance();
hintManager.clearShardingValues();
// 設(shè)置邏輯表 t_order 的分庫值
hintManager.addDatabaseShardingValue("t_order", 0);
// 設(shè)置邏輯表 t_order 的分表值
hintManager.addTableShardingValue("t_order", 1);
// 1%3 = 1 所以放入 db0.t_order_1 分片表
jdbcTemplate.execute("INSERT INTO `t_order`(`id`,`order_date`,`order_id`, `order_number`, `customer_id`, `total_amount`, `interval_value`, `user_id`) VALUES (1, '2024-03-20 00:00:00', 1, '1', 1, 1.00, '2024-01-01 00:00:00', 1);");
hintManager.close();
}
ShardingSphere 通過使用ThreadLocal管理強制路由配置,可以通過編程的方式向HintManager中添加分片值,該分片值僅在當(dāng)前線程內(nèi)生效。
- HintManager.getInstance() 獲取 HintManager 實例;
- HintManager.addDatabaseShardingValue,HintManager.addTableShardingValue 方法設(shè)置分片鍵值;
- 執(zhí)行 SQL 語句完成路由和執(zhí)行;
- 最后調(diào)用 HintManager.close 清理 ThreadLocal 中的內(nèi)容。
按我們設(shè)定的數(shù)據(jù)節(jié)點位置,插入一條測試數(shù)據(jù),看到確實存在了db0.t_order_1中,完美!
圖片
總結(jié)
本文中我們講解了ShardingSphere-jdbc所支持的12種分片算法,每種算法都具有獨特的特點。在實際應(yīng)用中,需要結(jié)合具體的業(yè)務(wù)場景來靈活選擇和應(yīng)用適合的分片算法。
由于篇幅已經(jīng)夠長了,剩下的 3 種自定義分片算法會在下一篇中詳細(xì)介紹。
全部demo案例 GitHub 地址:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/shardingsphere101/shardingsphere-algorithms