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

發表於2024-02-26

大家好,我是小富~

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

我是小富~ 下期見

相關文章