分庫分表如何管理不同實(shí)例中幾萬張分片表?
ShardingSphere實(shí)現(xiàn)分庫分表,如何管理分布在不同數(shù)據(jù)庫實(shí)例中的成千上萬張分片表?
上邊的問題是之前有個(gè)小伙伴看了我的分庫分表的文章,私下咨詢我的,看到他的提問我第一感覺就是這老鐵沒用過ShardingSphere,因?yàn)檫@個(gè)問題在ShardingSphere中已經(jīng)有了很好的解決方案,接下來看看怎么實(shí)現(xiàn)。
本文案例代碼GitHub地址:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/shardingsphere101/shardingsphere-autocreate-table
圖片
ShardingSphere學(xué)習(xí)路線
本文是《ShardingSphere5.x分庫分表原理與實(shí)戰(zhàn)》系列的第四篇文章,在進(jìn)行分庫分表設(shè)計(jì)時(shí),確認(rèn)好了數(shù)據(jù)節(jié)點(diǎn)數(shù)量和分片策略以后,接下來要做的就是管理大量的分片表。實(shí)際實(shí)施過程中可能存在上百個(gè)分片數(shù)據(jù)庫實(shí)例,每個(gè)實(shí)例中都可能有成千上萬個(gè)分片表,如果僅依靠人力來完成這些任務(wù)顯然是不現(xiàn)實(shí)的。所以,想要快速且自動(dòng)化管理這些分片表,使用工具是十分必要滴。
前言
ShardingSphere框架成員中的Shardingsphere-jdbc和Shardingsphere-proxy都提供了自動(dòng)化管理分片表的功能auto-tables,可以統(tǒng)一維護(hù)大量的分片表,避免了手動(dòng)編寫腳本和維護(hù)分片表的繁瑣工作,極大程度減少分庫分表的開發(fā)和維護(hù)成本,提升效率和可靠性。
圖片
這里咱們先使用Shardingsphere-jdbc來實(shí)際操作一下,Shardingsphere-proxy方式后續(xù)會(huì)有單獨(dú)的文章詳細(xì)講解,就不在這里展開了。
準(zhǔn)備工作
假設(shè)我們要對(duì)t_order表進(jìn)行分庫分表,首先我們要做的就是確定好分片方案,這里使用兩個(gè)數(shù)據(jù)庫實(shí)例db0、db1,每個(gè)實(shí)例中t_order表分成1000張分片表t_order_1 ~ t_order_1000,order_id字段作為分片鍵,分片算法使用取模算法order_id % n,分布式主鍵生成策略采用snowflake。
t_order邏輯表的表結(jié)構(gòu)如下:
CREATE TABLE `t_order` (
`order_id` BIGINT ( 20 ) NOT NULL COMMENT "訂單表分布式主健ID",
`order_number` VARCHAR ( 255 ) NOT NULL COMMENT "訂單號(hào)",
`customer_id` BIGINT ( 20 ) NOT NULL COMMENT "用戶ID",
`order_date` date NOT NULL COMMENT "下單時(shí)間",
`total_amount` DECIMAL ( 10, 2 ) NOT NULL COMMENT "訂單金額",
PRIMARY KEY ( `order_id` ) USING BTREE
);
有了這些基礎(chǔ)信息,可以先來進(jìn)行t_order表的分片配置了,不考慮其他因素,這里先Run起來!
分片規(guī)則配置
設(shè)定好分片規(guī)則,接著編寫邏輯表t_order的分片規(guī)則的配置,我分別使用yml配置和Java編碼兩種方式做了實(shí)現(xiàn)。要注意的是兩種方式不要并存,不然啟動(dòng)會(huì)報(bào)錯(cuò)。
yml配置方式
使用yml配置相對(duì)簡單易用比較直觀,適合對(duì)分庫分表要求不太復(fù)雜的場(chǎng)景,完整配置如下:
spring:
shardingsphere:
datasource:
# 數(shù)據(jù)源名稱,多數(shù)據(jù)源以逗號(hào)分隔 ,放在第一個(gè)的數(shù)據(jù)源為未配置分片規(guī)則表的默認(rèn)數(shù)據(jù)源
names: db0 , db1
# 名稱與上邊 names 保持一致
db0:
....
db1:
....
# 具體規(guī)則配置
rules:
sharding:
# 分片算法定義
sharding-algorithms:
# 自定義分片算法名稱
t_order_database_algorithms:
# 分片算法類型
type: INLINE
# 自定義參數(shù)
props:
algorithm-expression: db$->{order_id % 2}
t_order_table_algorithms:
type: INLINE
props:
algorithm-expression: t_order_$->{order_id % 1000}
t_order_mod:
type: MOD
props:
# 指定分片數(shù)量
sharding-count: 1000
# 分布式序列算法配置
key-generators:
t_order_snowflake:
type: SNOWFLAKE
# 分布式序列算法屬性配置
props:
worker-id: 1
tables:
# 邏輯表名稱
t_order:
# 數(shù)據(jù)節(jié)點(diǎn):數(shù)據(jù)庫.分片表
actual-data-nodes: db$->{0..1}.t_order_$->{1..1000}
# 分庫策略
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_algorithms
# 主鍵生成策略
keyGenerateStrategy:
column: order_id
keyGeneratorName: t_order_snowflake
# 屬性配置
props:
# 展示修改以后的sql語句
sql-show: true
Java編碼方式
使用Java編碼方式更加靈活和可擴(kuò)展,可以根據(jù)業(yè)務(wù)定制分片規(guī)則,適合對(duì)分庫分表有特殊需求或需要?jiǎng)討B(tài)調(diào)整的場(chǎng)景。
/**
* 公眾號(hào):程序員小富
*/
@Configuration
public class ShardingConfiguration {
/**
* 配置分片數(shù)據(jù)源
* 公眾號(hào):程序員小富
*/
@Bean
public DataSource getShardingDataSource() throws SQLException {
Map<String, DataSource> dataSourceMap = new HashMap<>();
dataSourceMap.put("db0", dataSource0());
dataSourceMap.put("db1", dataSource1());
// 分片rules規(guī)則配置
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
// 分片算法
shardingRuleConfig.setShardingAlgorithms(getShardingAlgorithms());
// 配置 t_order 表分片規(guī)則
ShardingTableRuleConfiguration orderTableRuleConfig = new ShardingTableRuleConfiguration("t_order", "db${0..1}.t_order_${1..1000}");
orderTableRuleConfig.setTableShardingStrategy(new StandardShardingStrategyConfiguration("order_id", "t_order_table_algorithms"));
orderTableRuleConfig.setDatabaseShardingStrategy(new StandardShardingStrategyConfiguration("order_id", "t_order_database_algorithms"));
shardingRuleConfig.getTables().add(orderTableRuleConfig);
// 是否在控制臺(tái)輸出解析改造后真實(shí)執(zhí)行的 SQL
Properties properties = new Properties();
properties.setProperty("sql-show", "true");
// 創(chuàng)建 ShardingSphere 數(shù)據(jù)源
return ShardingSphereDataSourceFactory.createDataSource(dataSourceMap, Collections.singleton(shardingRuleConfig), properties);
}
/**
* 配置數(shù)據(jù)源1
* 公眾號(hào):程序員小富
*/
public DataSource dataSource0() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/db0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimeznotallow=Asia/Shanghai&allowPublicKeyRetrieval=true");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
/**
* 配置數(shù)據(jù)源2
* 公眾號(hào):程序員小富
*/
public DataSource dataSource1() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/db1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimeznotallow=Asia/Shanghai&allowPublicKeyRetrieval=true");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
/**
* 配置分片算法
* 公眾號(hào):程序員小富
*/
private Map<String, AlgorithmConfiguration> getShardingAlgorithms() {
Map<String, AlgorithmConfiguration> shardingAlgorithms = new LinkedHashMap<>();
// 自定義分庫算法
Properties databaseAlgorithms = new Properties();
databaseAlgorithms.setProperty("algorithm-expression", "db$->{order_id % 2}");
shardingAlgorithms.put("t_order_database_algorithms", new AlgorithmConfiguration("INLINE", databaseAlgorithms));
// 自定義分表算法
Properties tableAlgorithms = new Properties();
tableAlgorithms.setProperty("algorithm-expression", "db$->{order_id % 1000}");
shardingAlgorithms.put("t_order_table_algorithms", new AlgorithmConfiguration("INLINE", tableAlgorithms));
return shardingAlgorithms;
}
}
上面我們?cè)趹?yīng)用中編寫好了分片規(guī)則,現(xiàn)在就差在數(shù)據(jù)庫實(shí)例中創(chuàng)建分片表了,手動(dòng)創(chuàng)建和管理1000張分片表確實(shí)是一個(gè)又臟又累的活,反正我是不會(huì)干的!
管理分片表
其實(shí),ShardingSphere內(nèi)已經(jīng)為我們提供了管理分片表的能力。
當(dāng)一張邏輯表t_order被配置了分片規(guī)則,那么接下來對(duì)邏輯表的各種DDL操作(例如創(chuàng)建表、修改表結(jié)構(gòu)等),命令和數(shù)據(jù)會(huì)根據(jù)分片規(guī)則,執(zhí)行和存儲(chǔ)到每個(gè)分片數(shù)據(jù)庫和分片庫中的相應(yīng)分片表中,以此保持整個(gè)分片環(huán)境的一致性。
不過,使用Shardingsphere-jdbc管理分片表的過程中,是需要我們手動(dòng)編寫對(duì)邏輯表的DDL操作的代碼。我們來跑幾個(gè)單元測(cè)試用例來觀察實(shí)際的執(zhí)行效果,直接使用jdbcTemplate執(zhí)行創(chuàng)建邏輯表t_order的SQL。
/**
* @author 公眾號(hào):程序員小富
* 自動(dòng)創(chuàng)建分片表
* @date 2023/12/31 17:25
*/
@SpringBootTest
class AutoCreateTablesTests {
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 執(zhí)行創(chuàng)建邏輯表的SQL,會(huì)根據(jù)AutoTables的配置自動(dòng)在對(duì)應(yīng)的數(shù)據(jù)源內(nèi)創(chuàng)建分片表
* @author 公眾號(hào):程序員小富
*/
@Test
public void autoCreateOrderTableTest() {
jdbcTemplate.execute("CREATE TABLE `t_order` (\n" +
" `order_id` bigint(20) NOT NULL,\n" +
" `order_number` varchar(255) NOT NULL,\n" +
" `customer_id` bigint(20) NOT NULL,\n" +
" `order_date` date NOT NULL,\n" +
" `total_amount` decimal(10,2) NOT NULL,\n" +
" PRIMARY KEY (`order_id`) USING BTREE\n" +
");");
}
}
根據(jù)之前配置的分片規(guī)則,將會(huì)在兩個(gè)數(shù)據(jù)庫實(shí)例 db0 和 db1 中,分別生成1000張命名為t_order_1到t_order_1000的分片表,看到兩個(gè)數(shù)據(jù)庫均成功創(chuàng)建了1000張分片表。
圖片
在次執(zhí)行更新t_order表SQL,將字段order_number長度從 varchar(255)擴(kuò)展到 varchar(500),執(zhí)行SQL看下效果。
/**
* @author 公眾號(hào):程序員小富
* 自動(dòng)創(chuàng)建分片表
* @date 2023/12/31 17:25
*/
@SpringBootTest
class AutoCreateTablesTests {
@Resource
private JdbcTemplate jdbcTemplate;
@Test
public void autoModifyOrderTableTest() {
jdbcTemplate.execute("ALTER TABLE t_order MODIFY COLUMN order_number varchar(500);");
}
}
通過查看兩個(gè)分片庫,我們成功地將所有分片表的order_number字段長度更改為了varchar(500),在控制臺(tái)日志中,可以看到它是通過在每個(gè)分片庫內(nèi)依次執(zhí)行了1000次命令實(shí)現(xiàn)的。
圖片
Shardingsphere-jdbc實(shí)現(xiàn)分庫分表時(shí),可以采用這種默認(rèn)的方式來管理分片表。但要注意的是,由于涉及到不同的數(shù)據(jù)庫實(shí)例,如果不使用第三方的分布式事務(wù)管理工具(例如Seata等),執(zhí)行過程是無法保證事務(wù)一致性的。
自定義管理分片表
上邊為邏輯表配置分片規(guī)則,應(yīng)用程序內(nèi)執(zhí)行對(duì)邏輯表的DDL操作,就可以很輕松的管理分片表。
自定義
不過,默認(rèn)的分片管理還是有局限性的,我們?cè)谠O(shè)計(jì)分片規(guī)則時(shí)往往會(huì)根據(jù)不同的業(yè)務(wù)維度來劃分,例如按天、月、按季度生成分片表并分布到不同數(shù)據(jù)源中等。這樣就需要一些自定義的規(guī)則來實(shí)現(xiàn)。
ShardingSphere 5.X版本后推出了一種新的管理分片配置方式:AutoTable。設(shè)置了AutoTable的邏輯表,將交由ShardingSphere自動(dòng)管理分片,用戶只需要指定分片數(shù)量和使用的數(shù)據(jù)庫實(shí)例,無需再關(guān)心表的具體分布,配置格式如下:
spring:
shardingsphere:
# 數(shù)據(jù)源配置
datasource:
......
# 具體規(guī)則配置
rules:
sharding:
# 邏輯表分片規(guī)則
tables:
# 邏輯表名稱
t_order:
.....
# 自動(dòng)分片表規(guī)則配置
auto-tables:
t_order: # 邏輯表名稱
actual-data-sources: db$->{0..1}
sharding-strategy: # 切分策略
standard: # 用于單分片鍵的標(biāo)準(zhǔn)分片場(chǎng)景
sharding-column: order_id # 分片列名稱
sharding-algorithm-name: t_order_mod # 自動(dòng)分片算法名稱
ShardingSphere-Jdbc中配置使用auto-tables主要兩個(gè)參數(shù),actual-data-sources指定數(shù)據(jù)源分布,由于是管理分片表所以只需數(shù)據(jù)源信息即可;sharding-strategy指具體采用何種算法來進(jìn)行分片。
對(duì)邏輯表的DDL操作,系統(tǒng)會(huì)首先檢查是否配置了AutoTable,如果已配置,則優(yōu)先采用配置的規(guī)則;若未配置,則將使用默認(rèn)的邏輯表分片規(guī)則。
AutoTable支持ShardingSphere內(nèi)置的全部自動(dòng)分片算法,所謂自動(dòng)分片算法就是根據(jù)actualDataSources設(shè)置的數(shù)據(jù)源信息,使用對(duì)應(yīng)內(nèi)置算法自行解析處理。
- MOD:取模分片算法
- HASH_MOD:哈希取模分片算法
- VOLUME_RANGE:基于分片容量的范圍分片算法
- BOUNDARY_RANGE:基于分片邊界的范圍分片算法
- AUTO_INTERVAL:自動(dòng)時(shí)間段分片算法
AutoTable使用
舉個(gè)例子,我們使用內(nèi)置MOD取模算法作為AutoTable的分片算法,同樣是db0、db1兩個(gè)實(shí)例中各創(chuàng)建1000張分片表。那么當(dāng)對(duì)邏輯表的DDL操作時(shí),ShardingSphere會(huì)依據(jù)分片表編號(hào)t_order_0~t_order_1999 % 數(shù)據(jù)庫實(shí)例數(shù)取模來確認(rèn)DDL命令路由到哪個(gè)實(shí)例中執(zhí)行。
spring:
shardingsphere:
# 數(shù)據(jù)源配置
datasource:
.....
# 具體規(guī)則配置
rules:
sharding:
# 自動(dòng)分片表規(guī)則配置
auto-tables:
t_order:
actual-data-sources: db$->{0..1}
sharding-strategy:
standard:
sharding-column: order_date
sharding-algorithm-name: t_order_mod
# 分片算法定義
sharding-algorithms:
t_order_mod:
type: MOD
props:
# 指定分片數(shù)量
sharding-count: 2000
還是執(zhí)行剛才創(chuàng)建表的單元測(cè)試,會(huì)發(fā)現(xiàn)db0、db1兩個(gè)實(shí)例中已經(jīng)各自創(chuàng)建了1000張分片表,但你會(huì)發(fā)現(xiàn)1000張表已經(jīng)不再是按照順序創(chuàng)建的了。
圖片
上邊使用的是內(nèi)置自動(dòng)分片算法,它對(duì)于我們來說是黑盒,提供它方便我們拿來即用。不過,如果想要做到更細(xì)粒度的管理分片表,最好的辦法就是自定義分片算法,后續(xù)章節(jié)會(huì)介紹所有內(nèi)置分片算法和自定義分片算法的使用。
總結(jié)
在使用ShardingSphere實(shí)現(xiàn)分庫分表的時(shí)候,要摒棄先建表、再配規(guī)則的傳統(tǒng)思維,要先確定規(guī)則在建表,管理表是一件很簡單的事,我們只要告訴ShardingSphere分片數(shù)量和分布規(guī)則,剩下的就讓框架來處理就好了。
本文案例代碼GitHub地址:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/shardingsphere101/shardingsphere-autocreate-table