【ShardingSphere技術專題】「ShardingJDBC」SpringBoot之整合ShardingJDBC實現分庫分表(JavaConfig方式)

李浩宇Alex發表於2021-08-20

前提介紹

ShardingSphere介紹

ShardingSphere是一套開源的分散式資料庫中介軟體解決方案組成的生態圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(計劃中)這3款相互獨立的產品組成。 他們均提供標準化的資料分片、分散式事務和資料庫治理功能,可適用於如Java同構、異構語言、雲原生等各種多樣化的應用場景。

shardingJDBC使用的範圍

  • 適用於任何基於JDBC的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
  • 支援任何第三方的資料庫連線池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
  • 支援任意實現JDBC規範的資料庫。目前支援MySQL,Oracle,SQLServer,PostgreSQL以及任何遵循SQL92標準的資料庫。

詳細一點的介紹直接看官網:https://shardingsphere.apache.org/document/current/cn/overview/

SQL語句相關

  • 邏輯表:水平拆分的資料庫(表)的相同邏輯和資料結構表的總稱。例:訂單資料根據主鍵尾數拆分為2張表,分別是t_order_0到t_order_1,他們的邏輯表名為t_order。

  • 真實表:在分片的資料庫中真實存在的物理表。例:示例中的t_order_0到t_order_1

  • 資料節點:資料分片的最小單元。由資料來源名稱和資料表組成,例:ds_0.t_order_0;ds_0.t_order_1;

  • 繫結表:指分片規則一致的主表和子表。例如:t_order表和t_order_item表,均按照order_id分片,則此兩張表互為繫結表關係。繫結表之間的多表關聯查詢不會出現笛卡爾積關聯,關聯查詢效率將大大提升。

  • 廣播表:指所有的分片資料來源中都存在的表,表結構和表中的資料在每個資料庫中均完全一致。適用於資料量不大且需要與海量資料的表進行關聯查詢的場景。

資料分片相關

  • 分片鍵:用於分片的資料庫欄位,是將資料庫(表)水平拆分的關鍵欄位。例:將訂單表中的訂單主鍵的尾數取模分片,則訂單主鍵為分片欄位。

SQL中如果無分片欄位,將執行全路由,效能較差。 除了對單分片欄位的支援,ShardingSphere也支援根據多個欄位進行分片。

  • 分片演算法:通過分片演算法將資料分片,支援通過=、>=、<=、>、<、BETWEEN和IN分片,分片演算法需要應用方開發者自行實現,可實現的靈活度非常高。

目前提供4種分片演算法

  • 精確分片演算法:對應PreciseShardingAlgorithm,用於處理使用單一鍵作為分片鍵的=與IN進行分片的場景。需要配合StandardShardingStrategy使用。
  • 範圍分片演算法:對應RangeShardingAlgorithm,用於處理使用單一鍵作為分片鍵的BETWEEN AND、>、<、>=、<=進行分片的場景。需要配合StandardShardingStrategy使用。
  • 複合分片演算法:對應ComplexKeysShardingAlgorithm,用於處理使用多鍵作為分片鍵進行分片的場景,包含多個分片鍵的邏輯較複雜,需要應用開發者自行處理其中的複雜度。需要配合ComplexShardingStrategy使用。
  • Hint分片演算法:對應HintShardingAlgorithm,用於處理使用Hint行分片的場景。需要配合HintShardingStrategy使用。

分片策略:包含分片鍵和分片演算法,由於分片演算法的獨立性,將其獨立抽離。真正可用於分片操作的是分片鍵 + 分片演算法,也就是分片策略。

目前提供5種分片策略

  • 標準分片策略:對應StandardShardingStrategy,提供對SQL語句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支援。

    • StandardShardingStrategy只支援單分片鍵,提供PreciseShardingAlgorithm和RangeShardingAlgorithm兩個分片演算法。
      • PreciseShardingAlgorithm是必選的,用於=和IN的分片
      • RangeShardingAlgorithm是可選的,用於BETWEEN AND, >, <, >=, <=分片,不配置RangeShardingAlgorithm,SQL中的BETWEEN AND將按照全庫路由處理。
  • 複合分片策略:對應ComplexShardingStrategy。提供對SQL語句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支援

    • ComplexShardingStrategy支援多分片鍵,由於多分片鍵之間的關係複雜,因此並未進行過多的封裝。
    • 而是直接將分片鍵值組合以及分片操作符透傳至分片演算法,完全由應用開發者實現,提供最大的靈活度。
  • 行表示式分片策略:對應InlineShardingStrategy。使用Groovy的表示式,提供對SQL語句中的=和IN的分片操作支援,只支援單分片鍵。

  • 對於簡單的分片演算法,可以通過簡單的配置使用,從而避免繁瑣的Java程式碼開發,如: t_user_$->{u_id % 8} 表示t_user表根據u_id模8,而分成8張表,表名稱為t_user_0到t_user_7。

  • Hint分片策略:對應HintShardingStrategy。通過Hint指定分片值而非從SQL中提取分片值的方式進行分片的策略。

  • 不分片策略:對應NoneShardingStrategy。

配置相關

分片規則:分片規則配置的總入口。包含資料來源配置、表配置、繫結表配置以及讀寫分離配置等。

  • 資料來源配置:真實資料來源列表。
  • 表配置:邏輯表名稱、資料節點與分表規則的配置
  • 資料節點配置:用於配置邏輯表與真實表的對映關係。
  • 分片策略配置:
    • 資料來源分片策略:對應於DatabaseShardingStrategy。用於配置資料被分配的目標資料來源。
    • 表分片策略:對應於TableShardingStrategy。用於配置資料被分配的目標表,該目標表存在與該資料的目標資料來源內。故表分片策略是依賴與資料來源分片策略的結果的。
  • 自增主鍵生成策略:通過在客戶端生成自增主鍵替換以資料庫原生自增主鍵的方式,做到分散式主鍵無重複。(雪花演算法)

開發步驟

開發整合方式

方式一:基於配置檔案整合,方便簡單但是不夠靈活

	<!--主要有以下依賴,分庫分表策略直接在application.properties做相關配置即可-->
	<dependency>
		<groupId>io.shardingsphere</groupId>
		<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
		<version>3.1.0.M1</version>
	</dependency>
	<dependency>
		<groupId>io.shardingsphere</groupId>
		<artifactId>sharding-jdbc-spring-namespace</artifactId>
		<version>3.1.0.M1</version>
	</dependency>

方式二:這裡我們主要基於java config的方式來整合到springboot中,更適合學習和理解

//相關依賴
<dependency>
	<groupId>io.shardingsphere</groupId>
	<artifactId>sharding-jdbc-core</artifactId>
	<version>3.1.0</version>
</dependency>
<!--<dependency>
	<groupId>io.shardingsphere</groupId>
	<artifactId>sharding-transaction-2pc-xa</artifactId>
	<version>3.1.0</version>
</dependency>-->
<dependency>
	<groupId>io.shardingsphere</groupId>
	<artifactId>sharding-jdbc-orchestration</artifactId>
	<version>3.1.0</version>
</dependency>
<dependency>
	<groupId>io.shardingsphere</groupId>
	<artifactId>sharding-orchestration-reg-zookeeper-curator</artifactId>
	<version>3.1.0</version>
</dependency>

定義相關配置類(DataSourceConfig => MybatisConfig => TransactionConfig)

ShardingSphereDataSourceConfig
import javax.sql.DataSource;
import java.lang.management.ManagementFactory;
import java.sql.SQLException;
import java.util.*;

/**
 * @Author zhangboqing
 * @Date 2020/4/25
 */
@Configuration
@Slf4j
public class ShardingSphereDataSourceConfig {

    @Bean("shardingDataSource")
    DataSource getShardingDataSource() throws SQLException {
        //初始化相關的分片規則配置資訊控制機制
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        // 設定相關的資料來源
        shardingRuleConfig.setDefaultDataSourceName("ds0");
       // 設定相關的Order表的相關的規則資訊配置機制
        shardingRuleConfig.getTableRuleConfigs().add(getOrderTableRuleConfiguration());
       // 設定相關的OrderItem表的相關的規則資訊配置機制
        shardingRuleConfig.getTableRuleConfigs().add(getOrderItemTableRuleConfiguration());
        // 配置繫結表關係
        shardingRuleConfig.getBindingTableGroups().add("t_order, t_order_item");
        // 廣播表操作機制
        shardingRuleConfig.getBroadcastTables().add("t_config");
        // 設定相關的分片機制策略(資料來源分片策略機制控制)
        shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(new InlineShardingStrategyConfiguration("user_id", "ds${user_id % 2}"));
        // 設定相關的分片策略機制,子啊inline模式下(包含了兩種模式)
        shardingRuleConfig.setDefaultTableShardingStrategyConfig(getShardingStrategyConfiguration());
        // ShardingPropertiesConstant相關配置選項
        Properties properties = new Properties();
        //是否列印SQL解析和改寫日誌
        properties.put("sql.show",true);
       //用於SQL執行的工作執行緒數量,為零則表示無限制
        propertie.setProperty("executor.size","4");
       //每個物理資料庫為每次查詢分配的最大連線數量
        propertie.setProperty("max.connections.size.per.query","1");
       //是否在啟動時檢查分表後設資料一致性
        propertie.setProperty("check.table.metadata.enabled","false");
		//使用者自定義屬性
        Map<String, Object> configMap = new HashMap<>();
        configMap.put("effect","分庫分表");
        return ShardingDataSourceFactory.createDataSource(createDataSourceMap(), shardingRuleConfig, properties);
    }

    // 配置相關的分片策略機制
    private ShardingStrategyConfiguration getShardingStrategyConfiguration(){
        // 精確匹配
        PreciseShardingAlgorithm<Long> preciseShardingAlgorithm = new PreciseShardingAlgorithm<Long>() {
            @Override
            public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {
                String prefix = shardingValue.getLogicTableName(); //邏輯表名稱
                Long orderId = shardingValue.getValue(); //訂單編碼
                long index = orderId % 2; //訂單表(分表路由索引)
                // t_order + "" + 0 = t_order0
                String tableName = prefix + "" +index;
                // 精確查詢、更新之類的,可以返回不存在表,進而給前端丟擲異常和警告。
                if (availableTargetNames.contains(tableName) == false) {
                    LogUtils.error(log,"PreciseSharding","orderId:{},不存在對應的資料庫表{}!", orderId, tableName);
                    return availableTargetNames.iterator().next();
                }
                return tableName;
//                return availableTargetNames.iterator().next();
            }
        };
        // 範圍匹配
        RangeShardingAlgorithm<Long> rangeShardingAlgorithm = new RangeShardingAlgorithm<Long>() {
            @Override
            public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Long> shardingValue) {
                String prefix = shardingValue.getLogicTableName();
                Collection<String> resList = new ArrayList<>();
                // 獲取相關的資料值範圍
                Range<Long> valueRange = shardingValue.getValueRange();
                // 如果沒有上限或者下限的沒有,則直接返回所有的資料表
                if (!valueRange.hasLowerBound() || !valueRange.hasUpperBound()) {
                    return availableTargetNames;
                }
               // 獲取下限資料範圍
                long lower = shardingValue.getValueRange().lowerEndpoint();
                BoundType lowerBoundType = shardingValue.getValueRange().lowerBoundType();
               // 獲取下限資料範圍
                long upper = shardingValue.getValueRange().upperEndpoint();
                BoundType upperBoundType = shardingValue.getValueRange().upperBoundType();
               // 下限資料資訊值
                long startValue = lower;
                long endValue = upper;
               // 是否屬於開區間(下限)
                if (lowerBoundType.equals(BoundType.OPEN)) {
                    startValue++; //縮減範圍1
                }
               // 是否屬於開區間(上限)
                if (upperBoundType.equals(BoundType.OPEN)) {
                    endValue--; // 縮減範圍1
                }
               // 進行計算相關所需要是實體表
                for (long i = startValue; i <= endValue ; i++) {
                    long index = i % 2;
                    String res = prefix + "" +index;
                    // 精確查詢、更新之類的,可以返回不存在表,進而給前端丟擲異常和警告。
                    if (availableTargetNames.contains(res) == false) {
                        LogUtils.error(log,"RangeSharding","orderId:{},不存在對應的資料庫表{}!", i, res);
                    }else{
                       resList.add(res);
                   }
                }
                if (resList.size() == 0) {
                    LogUtils.error(log,"RangeSharding","無法獲取對應表,因此將對全表進行查詢!orderId範圍為:{}到{}",startValue,endValue);
                    return availableTargetNames;
                }
                return resList;
            }
        };

        // 設定相關整體的演算法整合
        ShardingStrategyConfiguration strategyConf = new StandardShardingStrategyConfiguration("order_id", preciseShardingAlgorithm, rangeShardingAlgorithm);
        return strategyConf;
    }

    // 獲取相關的Order訂單規則表配置資訊控制配置控制機制
    TableRuleConfiguration getOrderTableRuleConfiguration() {
        // 邏輯表 + 實際節點 :設定邏輯表與資料節點(資料分片的最小單位)的對映關係機制
        TableRuleConfiguration result = new TableRuleConfiguration("t_order", "ds${0..1}.t_order${0..1}");
        // 主鍵生成配置
        result.setKeyGeneratorConfig(getKeyGeneratorConfigurationForTOrder());
        return result;
    }
    //主鍵操作的生成策略
    private KeyGeneratorConfiguration getKeyGeneratorConfigurationForTOrder() {
        Properties keyGeneratorProp = getKeyGeneratorProperties();
        return new KeyGeneratorConfiguration("SNOWFLAKE", "order_id", keyGeneratorProp);
    }

    // 獲取相關的Order訂單規則表配置資訊控制配置控制機制
    TableRuleConfiguration getOrderItemTableRuleConfiguration() {
        TableRuleConfiguration result = new TableRuleConfiguration("t_order_item", "ds${0..1}.t_order_item${0..1}");
        result.setKeyGeneratorConfig(getKeyGeneratorConfigurationForTOrderItem());
        return result;
    }

    // 建立相關keyOrderItem機制控制操作
	private KeyGeneratorConfiguration getKeyGeneratorConfigurationForTOrderItem() {
        Properties keyGeneratorProp = getKeyGeneratorProperties();
        return new KeyGeneratorConfiguration("SNOWFLAKE", "id", keyGeneratorProp);
    }

  // 生成鍵值相關的generator的配置資訊控制
    private Properties getKeyGeneratorProperties() {
        Properties keyGeneratorProp = new Properties();
        String distributeProcessIdentify = NetUtils.getLocalAddress() + ":" + getProcessId();
        String workId = String.valueOf(convertString2Long(distributeProcessIdentify));
        keyGeneratorProp.setProperty("worker.id", workId);
        LogUtils.info(log, "shardingsphere init", "shardingsphere work id raw string is {}, work id is {}", distributeProcessIdentify, workId);
        return keyGeneratorProp;
    }

    // 資料來源相關配置機制
    Map<String, DataSource> createDataSourceMap() {
        Map<String, DataSource> result = new HashMap<>();
        result.put("ds0", DataSourceUtils.createDataSource("ds0"));
        result.put("ds1", DataSourceUtils.createDataSource("ds1"));
        return result;
    }

	// 常見相關的workerid和dataid對應相關的程式id
    private String getProcessId(){
        String name = ManagementFactory.getRuntimeMXBean().getName();
        String pid = name.split("@")[0];
        return pid;
    }

	// 轉換字串成為相關的long型別
    private Long convertString2Long(String str){
        long hashCode = str.hashCode() + System.currentTimeMillis();
        if(hashCode < 0){
            hashCode = -hashCode;
        }
        return hashCode % (1L << 10);
    }
}
ShardingsphereMybatisConfig 配置機制
/**
 * @Author zhangboqing
 * @Date 2020/4/23
 */
@Configuration
@MapperScan(basePackages = "com.zbq.springbootshardingjdbcjavaconfigdemo.dao",sqlSessionFactoryRef = "sqlSessionFactoryForShardingjdbc")
public class ShardingsphereMybatisConfig {
    @Autowired
    @Qualifier("shardingDataSource")
    private DataSource dataSource;
    @Bean("sqlSessionFactoryForShardingjdbc")
    public SqlSessionFactory sqlSessionFactoryForShardingjdbc() throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
//        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().
//                getResources("classpath*:**/*.xml"));
        sessionFactory.setTypeAliasesPackage("com.zbq.springbootshardingjdbcjavaconfigdemo.domain.entity");
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(true);
        sessionFactory.setConfiguration(configuration);
        return sessionFactory.getObject();
    }
}
ShardingsphereTransactionConfig 配置機制

主要定製化配置事務操作可以空戰未來的,為了未來的查詢擴充套件XA


@Configuration
@EnableTransactionManagement
public class ShardingsphereTransactionConfig {
    @Bean
    @Autowired
    public PlatformTransactionManager shardingsphereTransactionManager(@Qualifier("shardingDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

相關文章