【Spring Boot】Spring Boot之整合Sharding-JDBC(java config方式)實現分庫分表(水平拆分)


一、概念先行

1)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分片,則此兩張表互為綁定表關系。綁定表之間的多表關聯查詢不會出現笛卡爾積關聯,關聯查詢效率將大大提升。
  • 廣播表:指所有的分片數據源中都存在的表,表結構和表中的數據在每個數據庫中均完全一致。適用於數據量不大且需要與海量數據的表進行關聯查詢的場景,例如:字典表,示例中的t

2)分片相關

  • 分片鍵:用於分片的數據庫字段,是將數據庫(表)水平拆分的關鍵字段。例:將訂單表中的訂單主鍵的尾數取模分片,則訂單主鍵為分片字段。 SQL中如果無分片字段,將執行全路由,性能較差。 除了對單分片字段的支持,ShardingSphere也支持根據多個字段進行分片。
  • 分片算法:通過分片算法將數據分片,支持通過=、>=、<=、>、<、BETWEEN和IN分片。分片算法需要應用方開發者自行實現,可實現的靈活度非常高。
    目前提供4種分片算法:
  1. 精確分片算法:對應PreciseShardingAlgorithm,用於處理使用單一鍵作為分片鍵的=與IN進行分片的場景。需要配合StandardShardingStrategy使用。
  2. 范圍分片算法:對應RangeShardingAlgorithm,用於處理使用單一鍵作為分片鍵的BETWEEN AND、>、<、>=、<=進行分片的場景。需要配合StandardShardingStrategy使用。
  3. 復合分片算法:對應ComplexKeysShardingAlgorithm,用於處理使用多鍵作為分片鍵進行分片的場景,包含多個分片鍵的邏輯較復雜,需要應用開發者自行處理其中的復雜度。需要配合ComplexShardingStrategy使用。
  4. Hint分片算法:對應HintShardingAlgorithm,用於處理使用Hint行分片的場景。需要配合HintShardingStrategy使用。
  • 分片策略:包含分片鍵和分片算法,由於分片算法的獨立性,將其獨立抽離。真正可用於分片操作的是分片鍵 + 分片算法,也就是分片策略。

    目前提供5種分片策略。

  1. 標准分片策略:對應StandardShardingStrategy。提供對SQL語句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持單分片鍵,提供PreciseShardingAlgorithm和RangeShardingAlgorithm兩個分片算法。PreciseShardingAlgorithm是必選的,用於處理=和IN的分片。RangeShardingAlgorithm是可選的,用於處理BETWEEN AND, >, <, >=, <=分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND將按照全庫路由處理。
  2. 復合分片策略:對應ComplexShardingStrategy。復合分片策略。提供對SQL語句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片鍵,由於多分片鍵之間的關系復雜,因此並未進行過多的封裝,而是直接將分片鍵值組合以及分片操作符透傳至分片算法,完全由應用開發者實現,提供最大的靈活度。
  3. 行表達式分片策略:對應InlineShardingStrategy。使用Groovy的表達式,提供對SQL語句中的=和IN的分片操作支持,只支持單分片鍵。對於簡單的分片算法,可以通過簡單的配置使用,從而避免繁瑣的Java代碼開發,如: t_user_$->{u_id % 8} 表示t_user表根據u_id模8,而分成8張表,表名稱為t_user_0到t_user_7。
  4. Hint分片策略:對應HintShardingStrategy。通過Hint指定分片值而非從SQL中提取分片值的方式進行分片的策略。
  5. 不分片策略:對應NoneShardingStrategy。不分片的策略。

3)配置相關

  • 分片規則:分片規則配置的總入口。包含數據源配置、表配置、綁定表配置以及讀寫分離配置等。
  • 數據源配置:真實數據源列表。
  • 表配置:邏輯表名稱、數據節點與分表規則的配置
  • 數據節點配置:用於配置邏輯表與真實表的映射關系。
  • 分片策略配置:
    數據源分片策略:對應於DatabaseShardingStrategy。用於配置數據被分配的目標數據源。
    表分片策略:對應於TableShardingStrategy。用於配置數據被分配的目標表,該目標表存在與該數據的目標數據源內。故表分片策略是依賴與數據源分片策略的結果的。
  • 自增主鍵生成策略:通過在客戶端生成自增主鍵替換以數據庫原生自增主鍵的方式,做到分布式主鍵無重復。(雪花算法)

二、整合步驟

1)引入相關Maven坐標

  <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-core</artifactId>
            <version>4.0.1</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>

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

  1. 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");
            shardingRuleConfig.getTableRuleConfigs().add(getOrderTableRuleConfiguration());
            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}"));
            shardingRuleConfig.setDefaultTableShardingStrategyConfig(getShardingStrategyConfiguration());
            // ShardingPropertiesConstant相關配置選項
            Properties properties = new Properties();
            properties.put("sql.show",true);
            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++;
                    }
                    if (upperBoundType.equals(BoundType.OPEN)) {
                        endValue--;
                    }
    
                    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);
                            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;
        }
    
    
    
        TableRuleConfiguration getOrderTableRuleConfiguration() {
            // 邏輯表 + 實際節點
            TableRuleConfiguration result = new TableRuleConfiguration("t_order", "ds${0..1}.t_order${0..1}");
            // 主鍵生成配置
            result.setKeyGeneratorConfig(getKeyGeneratorConfigurationForTOrder());
            return result;
        }
    
        TableRuleConfiguration getOrderItemTableRuleConfiguration() {
            TableRuleConfiguration result = new TableRuleConfiguration("t_order_item", "ds${0..1}.t_order_item${0..1}");
            result.setKeyGeneratorConfig(getKeyGeneratorConfigurationForTOrderItem());
            return result;
        }
    
        Map<String, DataSource> createDataSourceMap() {
            Map<String, DataSource> result = new HashMap<>();
            result.put("ds0", DataSourceUtils.createDataSource("ds0"));
            result.put("ds1", DataSourceUtils.createDataSource("ds1"));
            return result;
        }
    
        private KeyGeneratorConfiguration getKeyGeneratorConfigurationForTOrder() {
            Properties keyGeneratorProp = getKeyGeneratorProperties();
            return new KeyGeneratorConfiguration("SNOWFLAKE", "order_id", keyGeneratorProp);
        }
    
        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;
        }
    
        private KeyGeneratorConfiguration getKeyGeneratorConfigurationForTOrderItem() {
            Properties keyGeneratorProp = getKeyGeneratorProperties();
            return new KeyGeneratorConfiguration("SNOWFLAKE", "id", keyGeneratorProp);
        }
    
        private String getProcessId(){
            String name = ManagementFactory.getRuntimeMXBean().getName();
            String pid = name.split("@")[0];
            return pid;
        }
    
        private Long convertString2Long(String str){
            long hashCode = str.hashCode() + System.currentTimeMillis();
            if(hashCode < 0){
                hashCode = -hashCode;
            }
            return hashCode % (1L << 10);
        }
    
    
    }
  2. 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();
        }
    
    }
  3. ShardingsphereTransactionConfig
    /**
     * @Author zhangboqing
     * @Date 2020/4/25
     */
    @Configuration
    @EnableTransactionManagement
    public class ShardingsphereTransactionConfig {
    
        @Bean
        @Autowired
        public PlatformTransactionManager shardingsphereTransactionManager(@Qualifier("shardingDataSource") DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    }  

3)測試

1.定義dao

@Mapper
public interface TOrderDao {

    @Insert("insert into t_order values(#{orderId},#{orderNo},#{userId})")
    public int insert(@Param("orderId") Long orderId, @Param("orderNo") String orderNo,@Param("userId") Long userId);

    @Insert("insert into t_order(order_no,user_id) values(#{orderNo},#{userId})")
    public int insert2(@Param("orderNo") String orderNo,@Param("userId") Long userId);

    @Insert("insert into t_order_item(user_id,order_id,item_id) values(#{userId},#{orderId},#{itemId})")
    public int insertOrderItems(@Param("userId") Long userId,@Param("orderId") Long orderId,@Param("itemId") Long itemId);

    @Select("select * from t_order where order_id > #{startValue}")
    public List<TOrder> findList(Long startValue);
    @Select("select * from t_order as a left join  t_order_item as b on b.order_id = a.order_id left join t_order_config c on c.order_id = a.order_id where a.order_id = 460845380954202112 ")
    public List<Map> findAll();
}

2.test類

@SpringBootTest
class SpringbootShardingjdbcJavaconfigDemoApplicationTests {

    @Autowired
    private TOrderDao tOrderDao;

    @Test
    void contextLoads() {
//        for (int i = 0; i < 2; i++) {
//            int insert = tOrderDao.insert2( "11111",1L);
//            System.out.println(insert);
//        }

//        List<TOrder> list = tOrderDao.findList(1L);
//        System.out.println(list);

//        tOrderDao.insertOrderItems(1L,460845380954202112L,1L);
        List<Map> all = tOrderDao.findAll();
        System.out.println(all);
    }
}

  

其他:
官方網站:https://shardingsphere.apache.org/document/current/en/overview/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM