zoukankan      html  css  js  c++  java
  • 【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/

  • 相关阅读:
    引用kernel32.dll中的API来进行串口通讯
    vs2017 项目生成时不产生xml文件的方法
    session的处理机制
    用户未登录或Session超时时重定向到登录页,不那么简单
    VS C# debug文件夹中各文件的作用
    Tomcat(免安装版)的安装与配置【转】
    关于C#关闭窗体后,依旧有后台进程在运行的解决方法
    DatakeyNames和datakey
    ASP.NET页面生命周期描述
    比较C#中几种常见的复制字节数组方法的效率
  • 原文地址:https://www.cnblogs.com/756623607-zhang/p/12775011.html
Copyright © 2011-2022 走看看