分庫分表如何管理不同實例中幾萬張分片表?

大家好,我是小富~

ShardingSphere實現分庫分表,如何管理分佈在不同數據庫實例中的成千上萬張分片表?

上邊的問題是之前有個小夥伴看了我的分庫分表的文章,私下諮詢我的,看到他的提問我第一感覺就是這老鐵沒用過ShardingSphere,因爲這個問題在ShardingSphere中已經有了很好的解決方案,接下來看看怎麼實現。

本文案例代碼GitHub地址:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/shardingsphere101/shardingsphere-autocreate-table

系列往期

往期系列文章(我佛系更新,無下限拖更):

(一)好好的系統,爲什麼要分庫分表?

(二)分庫分表的 21 條法則,hold 住!

(三)2 種方式快速實現分庫分表,輕鬆拿捏!

ShardingSphere學習路線

本文是《ShardingSphere5.x分庫分表原理與實戰》系列的第四篇文章,在進行分庫分表設計時,確認好了數據節點數量和分片策略以後,接下來要做的就是管理大量的分片表。實際實施過程中可能存在上百個分片數據庫實例,每個實例中都可能有成千上萬個分片表,如果僅依靠人力來完成這些任務顯然是不現實的。所以,想要快速且自動化管理這些分片表,使用工具是十分必要滴。

前言

ShardingSphere框架成員中的Shardingsphere-jdbcShardingsphere-proxy都提供了自動化管理分片表的功能auto-tables,可以統一維護大量的分片表,避免了手動編寫腳本和維護分片表的繁瑣工作,極大程度減少分庫分表的開發和維護成本,提升效率和可靠性。

這裏咱們先使用Shardingsphere-jdbc來實際操作一下,Shardingsphere-proxy方式後續會有單獨的文章詳細講解,就不在這裏展開了。

準備工作

假設我們要對t_order表進行分庫分表,首先我們要做的就是確定好分片方案,這裏使用兩個數據庫實例db0db1,每個實例中t_order表分成1000張分片表t_order_1 ~ t_order_1000order_id字段作爲分片鍵,分片算法使用取模算法order_id % n,分佈式主鍵生成策略採用snowflake

t_order邏輯表的表結構如下:

CREATE TABLE `t_order` (
	`order_id` BIGINT ( 20 ) NOT NULL COMMENT "訂單表分佈式主健ID",
	`order_number` VARCHAR ( 255 ) NOT NULL COMMENT "訂單號",
	`customer_id` BIGINT ( 20 ) NOT NULL COMMENT "用戶ID",
	`order_date` date NOT NULL COMMENT "下單時間",
	`total_amount` DECIMAL ( 10, 2 ) NOT NULL COMMENT "訂單金額",
    PRIMARY KEY ( `order_id` ) USING BTREE 
);

有了這些基礎信息,可以先來進行t_order表的分片配置了,不考慮其他因素,這裏先Run起來!

分片規則配置

設定好分片規則,接着編寫邏輯表t_order的分片規則的配置,我分別使用yml配置Java編碼兩種方式做了實現。要注意的是兩種方式不要並存,不然啓動會報錯

yml配置方式

使用yml配置相對簡單易用比較直觀,適合對分庫分表要求不太複雜的場景,完整配置如下:

spring:
  shardingsphere:
    datasource:
      # 數據源名稱,多數據源以逗號分隔 ,放在第一個的數據源爲未配置分片規則表的默認數據源
      names: db0 , db1
      # 名稱與上邊 names 保持一致
      db0:
      ....

      db1:
      ....
    # 具體規則配置
    rules:
      sharding:
        # 分片算法定義
        sharding-algorithms:
          # 自定義分片算法名稱
          t_order_database_algorithms:
            # 分片算法類型
            type: INLINE
            # 自定義參數
            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:
              # 指定分片數量
              sharding-count: 1000
        # 分佈式序列算法配置
        key-generators:
          t_order_snowflake:
            type: SNOWFLAKE
            # 分佈式序列算法屬性配置
            props:
              worker-id: 1
        tables:
          # 邏輯表名稱
          t_order:
            # 數據節點:數據庫.分片表
            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編碼方式更加靈活和可擴展,可以根據業務定製分片規則,適合對分庫分表有特殊需求或需要動態調整的場景。

/**
 * 公衆號:程序員小富
 */
@Configuration
public class ShardingConfiguration {

    /**
     * 配置分片數據源
     * 公衆號:程序員小富
     */
    @Bean
    public DataSource getShardingDataSource() throws SQLException {
        Map<String, DataSource> dataSourceMap = new HashMap<>();
        dataSourceMap.put("db0", dataSource0());
        dataSourceMap.put("db1", dataSource1());

        // 分片rules規則配置
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();

        // 分片算法
        shardingRuleConfig.setShardingAlgorithms(getShardingAlgorithms());
        // 配置 t_order 表分片規則
        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);

        // 是否在控制檯輸出解析改造後真實執行的 SQL
        Properties properties = new Properties();
        properties.setProperty("sql-show", "true");

        // 創建 ShardingSphere 數據源
        return ShardingSphereDataSourceFactory.createDataSource(dataSourceMap, Collections.singleton(shardingRuleConfig), properties);
    }

    /**
     * 配置數據源1
     * 公衆號:程序員小富
     */
    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&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }

    /**
     * 配置數據源2
     * 公衆號:程序員小富
     */
    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&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }

    /**
     * 配置分片算法
     * 公衆號:程序員小富
     */
    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;
    }
}

上面我們在應用中編寫好了分片規則,現在就差在數據庫實例中創建分片表了,手動創建和管理1000張分片表確實是一個又髒又累的活,反正我是不會幹的!

管理分片表

其實,ShardingSphere內已經爲我們提供了管理分片表的能力。

當一張邏輯表t_order被配置了分片規則,那麼接下來對邏輯表的各種DDL操作(例如創建表修改表結構等),命令和數據會根據分片規則,執行和存儲到每個分片數據庫和分片庫中的相應分片表中,以此保持整個分片環境的一致性。

不過,使用Shardingsphere-jdbc管理分片表的過程中,是需要我們手動編寫對邏輯表的DDL操作的代碼。我們來跑幾個單元測試用例來觀察實際的執行效果,直接使用jdbcTemplate執行創建邏輯表t_order的SQL。

/**
 * @author 公衆號:程序員小富
 * 自動創建分片表
 * @date 2023/12/31 17:25
 */
@SpringBootTest
class AutoCreateTablesTests {
    @Resource
    private JdbcTemplate jdbcTemplate;
    /**
     * 執行創建邏輯表的SQL,會根據AutoTables的配置自動在對應的數據源內創建分片表
     * @author 公衆號:程序員小富
     */
    @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" +
                ");");
    }
}

根據之前配置的分片規則,將會在兩個數據庫實例 db0db1 中,分別生成1000張命名爲t_order_1t_order_1000的分片表,看到兩個數據庫均成功創建了1000張分片表。

在次執行更新t_order表SQL,將字段order_number長度從 varchar(255)擴展到 varchar(500),執行SQL看下效果。

/**
 * @author 公衆號:程序員小富
 * 自動創建分片表
 * @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);");
    }
}

通過查看兩個分片庫,我們成功地將所有分片表的order_number字段長度更改爲了varchar(500),在控制檯日誌中,可以看到它是通過在每個分片庫內依次執行了1000次命令實現的。

Shardingsphere-jdbc實現分庫分表時,可以採用這種默認的方式來管理分片表。但要注意的是,由於涉及到不同的數據庫實例,如果不使用第三方的分佈式事務管理工具(例如Seata等),執行過程是無法保證事務一致性的。

自定義管理分片表

上邊爲邏輯表配置分片規則,應用程序內執行對邏輯表的DDL操作,就可以很輕鬆的管理分片表。

自定義

不過,默認的分片管理還是有侷限性的,我們在設計分片規則時往往會根據不同的業務維度來劃分,例如按天、月、按季度生成分片表並分佈到不同數據源中等。這樣就需要一些自定義的規則來實現。

ShardingSphere 5.X版本後推出了一種新的管理分片配置方式:AutoTable。設置了AutoTable的邏輯表,將交由ShardingSphere自動管理分片,用戶只需要指定分片數量和使用的數據庫實例,無需再關心表的具體分佈,配置格式如下:

spring:
  shardingsphere:
    # 數據源配置
    datasource:
      ......
    # 具體規則配置
    rules:
      sharding:
        # 邏輯表分片規則
        tables:
          # 邏輯表名稱
          t_order:
            .....
        # 自動分片表規則配置
        auto-tables:
          t_order: # 邏輯表名稱
            actual-data-sources: db$->{0..1}
            sharding-strategy: # 切分策略
              standard: # 用於單分片鍵的標準分片場景
                sharding-column: order_id # 分片列名稱
                sharding-algorithm-name: t_order_mod # 自動分片算法名稱

ShardingSphere-Jdbc中配置使用auto-tables主要兩個參數,actual-data-sources指定數據源分佈,由於是管理分片表所以只需數據源信息即可;sharding-strategy指具體採用何種算法來進行分片。

對邏輯表的DDL操作,系統會首先檢查是否配置了AutoTable,如果已配置,則優先採用配置的規則;若未配置,則將使用默認的邏輯表分片規則。

AutoTable支持ShardingSphere內置的全部自動分片算法,所謂自動分片算法就是根據actualDataSources設置的數據源信息,使用對應內置算法自行解析處理。

  • MOD:取模分片算法
  • HASH_MOD:哈希取模分片算法
  • VOLUME_RANGE:基於分片容量的範圍分片算法
  • BOUNDARY_RANGE:基於分片邊界的範圍分片算法
  • AUTO_INTERVAL:自動時間段分片算法

AutoTable使用

舉個例子,我們使用內置MOD取模算法作爲AutoTable的分片算法,同樣是db0db1兩個實例中各創建1000張分片表。那麼當對邏輯表的DDL操作時,ShardingSphere會依據分片表編號t_order_0~t_order_1999 % 數據庫實例數取模來確認DDL命令路由到哪個實例中執行。


spring:
  shardingsphere:
    # 數據源配置
    datasource:
      .....
    # 具體規則配置
    rules:
      sharding:
        # 自動分片表規則配置
        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:
              # 指定分片數量
              sharding-count: 2000

還是執行剛纔創建表的單元測試,會發現db0db1兩個實例中已經各自創建了1000張分片表,但你會發現1000張表已經不再是按照順序創建的了。

上邊使用的是內置自動分片算法,它對於我們來說是黑盒,提供它方便我們拿來即用。不過,如果想要做到更細粒度的管理分片表,最好的辦法就是自定義分片算法,後續章節會介紹所有內置分片算法和自定義分片算法的使用

總結

在使用ShardingSphere實現分庫分表的時候,要摒棄先建表、再配規則的傳統思維,要先確定規則在建表,管理表是一件很簡單的事,我們只要告訴ShardingSphere分片數量和分佈規則,剩下的就讓框架來處理就好了。

本文案例代碼GitHub地址:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/shardingsphere101/shardingsphere-autocreate-table

我是小富~ 下期見

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章