ShardingSphere原理分析和实战总结(一)

简介: ShardingSphere原理分析和实战总结

架构原理

官方文档:概览 :: ShardingSphere

核心概念

逻辑表

水平拆分的数据库(表)的相同逻辑和数据结构表的总称。例:订单数据根据主键尾数拆分为10张表,分别是t_order_0t_order_9,他们的逻辑表名为t_order

真实表

在分片的数据库中真实存在的物理表。即上个示例中的t_order_0t_order_9

数据节点

数据分片的最小单元。由数据源名称和数据表组成,例:ds_0.t_order_0

绑定表

指分片规则一致的主表和子表。例如:t_order表和t_order_item表,均按照order_id分片,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。举例说明,如果SQL为:

SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

在不配置绑定表关系时,假设分片键order_id将数值10路由至第0片,将数值11路由至第1片,那么路由后的SQL应该为4条,它们呈现为笛卡尔积:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

在配置绑定表关系后,路由的SQL应该为2条:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

其中t_order在FROM的最左侧,ShardingSphere将会以它作为整个绑定表的主表。 所有路由计算将会只使用主表的策略,那么t_order_item表的分片计算将会使用t_order的条件。故绑定表之间的分区键要完全相同。

分片策略

ShardingSphere认为对于分片策略存有两种维度:

  • 数据源分片策略(DatabaseShardingStrategy):数据被分配的目标数据源
  • 表分片策略(TableShardingStrategy):数据被分配的目标表

两种分片策略API完全相同,但是表分片策略是依赖于数据源分片策略的(即:先分库然后才有分表)

分片算法

Sharding-JDBC提供了5种分片策略。由于分片算法和业务实现紧密相关,因此Sharding-JDBC并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。

  • 精确分片算法

对应PreciseShardingAlgorithm,用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用。

  • 范围分片算法

对应RangeShardingAlgorithm,用于处理使用单一键作为分片键的BETWEEN AND、>、<、>=、<=进行分片的场景。需要配合StandardShardingStrategy使用。

  • 复合分片算法

对应ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合ComplexShardingStrategy使用。

  • Hint分片算法

对应HintShardingAlgorithm,用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。

分页原理

select * from ORDER_XX where orderId = ? limit 0 ,10

如果查询条件没有orderId, 那么最终执行的sql就是3条(假设每页10条):

select * from ORDER_00 where create_tm >= ?  and create_tm <= ? limit 0 ,10 ;
select * from ORDER_01 where create_tm >= ?  and create_tm <= ? limit 0 ,10 ;
select * from ORDER_02 where create_tm >= ?  and create_tm <= ? limit 0 ,10 ;

注意在有多个分表的情况下, 每个表都取前10条数据出来(一共30条), 然后再排序取前10条, 这样的逻辑是不对的. sharding-jdbc给了个例子, 如果下图:

图中的例子中,想要取得两个表中共同的按照分数排序的第2条和第3条数据,应该是95和90。 由于执行的SQL只能从每个表中获取第2条和第3条数据,即从t_score_0表中获取的是90和80;从t_score_1表中获取的是85和75。 因此进行结果归并时,只能从获取的90,80,85和75之中进行归并,那么结果归并无论怎么实现,都不可能获得正确的结果.

那怎么办呢?

sharding-jdbc的做法就改写我们的sql, 先查出来所有的数据, 再做归并排序

例如查询第2页时

原sql是:
select * from ORDER_00 where create_tm >= ?  and create_tm <= ? limit 10 ,10 ;
select * from ORDER_01 where create_tm >= ?  and create_tm <= ? limit 10 ,10 ;
select * from ORDER_02 where create_tm >= ?  and create_tm <= ? limit 10 ,10 ;
会被改写成:
select * from ORDER_00 where create_tm >= ?  and create_tm <= ? limit 0 ,20 ;
select * from ORDER_01 where create_tm >= ?  and create_tm <= ? limit 0 ,20 ;
select * from ORDER_02 where create_tm >= ?  and create_tm <= ? limit 0 ,20 ;

查询第3页时

原sql是:
select * from ORDER_00 where create_tm >= ?  and create_tm <= ? limit 20 ,10 ;
select * from ORDER_01 where create_tm >= ?  and create_tm <= ? limit 20 ,10 ;
select * from ORDER_02 where create_tm >= ?  and create_tm <= ? limit 20 ,10 ;
会被改写成:
select * from ORDER_00 where create_tm >= ?  and create_tm <= ? limit 0 ,30 ;
select * from ORDER_01 where create_tm >= ?  and create_tm <= ? limit 0 ,30 ;
select * from ORDER_02 where create_tm >= ?  and create_tm <= ? limit 0 ,30 ;

当然, 大家肯定会觉得这样处理性能会很差, 其实事实上也的确是, 不过sharing-jdbc是在这个基础上做了优化的,就是上面提到的"归并",大概的逻辑就是先查出所有页的数据, 然后通过流式处理跳过前面的页,只取最终需要的页,最终达到分页的目的

配置实战

Sharding-JDBC重写了mybatis里配置sql的路由规则,只要按照常规的方法在xml文件里书写sql就可以自动路由的对应的表,但是查询时最好要指定分片键,否则会出现扫描所有表的情况,在数据量很庞大的情况下不可取。

引入依赖

<!--mybatis-->
<dependency>
     <groupId>org.apache.shardingsphere</groupId>
     <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
     <version>${sharding.jdbc.version}</version>
</dependency>
<dependency>
     <groupId>tk.mybatis</groupId>
     <artifactId>mapper-spring-boot-starter</artifactId>
     <version>${tk.mybatis.version}</version>
</dependency>

数据源配置 (按照月份进行分表)

spring:
  shardingsphere:
    datasource:
      names: two-project
      two-project:
        driver-class-name: com.mysql.jdbc.Driver
        filters: stat,wall,log4j
        poolPreparedStatements: true
        stat-view-servlet:
          enabled: true
          reset-enable: false
          url-pattern: /druid/*
        testOnBorrow: false
        testOnReturn: false
        testWhileIdle: true
        type: com.alibaba.druid.pool.DruidDataSource
        url: jdbc:mysql://127.0.0.1:3306/sharding-jdbc?useSSL=false&characterEncoding=utf-8&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
        username: root
        password: 123456..
        validationQuery: SELECT 1 FROM DUAL
        web-stat-filter:
          enabled: true
          exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
    props:
      sql:
        show: false
    sharding:
      tables:
        order:
          actual-data-nodes: two-project.t_order_2020_0$->{8..9},two-project.t_order_2020_$->{10..12},two-project.t_order_$->{2021..2099}_0$->{1..9},two-project.t_order_$->{2021..2099}_$->{10..12}
          table-strategy:
            standard:
              sharding-column: id
              precise-algorithm-class-name: com.baoquan.sharding.TimeShardingTableAlgorithm
              range-algorithm-class-name: com.baoquan.sharding.TimeRangeShardingAlgorithm

mapper文件

<insert id="createOrder" parameterType="com.baoquan.dataobject.po.Order">
   insert into `order` (id,user_id, order_id,create_time) values (#{id},#{userId}, #{orderId}, #{createTime})
</insert>

分片算法

package com.baoquan.sharding;
import com.baoquan.constants.CommonContants;
import com.baoquan.util.ParaseShardingKeyTool;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import java.util.Collection;
public class TimeShardingTableAlgorithm implements PreciseShardingAlgorithm<Long> {
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {
        String logicTable = new StringBuffer().append(shardingValue.getLogicTableName())
                .append(CommonContants.SPLIT).append(ParaseShardingKeyTool.getYearAndMonth((shardingValue.getValue().toString()))).toString();
        //获取实际表名
        String targetTable = null;
        for (String availableTargetName : availableTargetNames) {
            if (availableTargetName.contains(logicTable)) {
                targetTable = availableTargetName;
            }
        }
        return targetTable;
    }
}
package com.baoquan.sharding;
import com.google.common.collect.Range;
import com.baoquan.constants.CommonContants;
import com.baoquan.util.ParaseShardingKeyTool;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Collection;
import java.util.LinkedHashSet;
public class TimeRangeShardingAlgorithm implements RangeShardingAlgorithm<String> {
    private DateTimeFormatter dateformat = DateTimeFormatter.ofPattern(CommonContants.yyyy_MM);
    private DateTimeFormatter DATE_FORMAT = new DateTimeFormatterBuilder()
                    .appendPattern(CommonContants.yyyy_MM)
                    .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
                    .toFormatter();
    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<String> shardingValue) {
        Collection<String> result = new LinkedHashSet<String>();
        Range<String> shardingKey = shardingValue.getValueRange();
        String startShardingKey = shardingKey.lowerEndpoint();
        String endShardingKey = shardingKey.upperEndpoint();
        String startTimeString = ParaseShardingKeyTool.getYearAndMonth(startShardingKey);
        String endTimeString = ParaseShardingKeyTool.getYearAndMonth(endShardingKey);
        LocalDate startLocalDate = LocalDate.parse(startTimeString, DATE_FORMAT);
        LocalDate endLocalDate = LocalDate.parse(endTimeString, DATE_FORMAT);
        while (startLocalDate.compareTo(endLocalDate) != 0) {
            StringBuffer tableName = new StringBuffer();
            tableName.append(shardingValue.getLogicTableName())
                    .append(CommonContants.SPLIT).append(dateformat.format(startLocalDate));
            result.add(tableName.toString());
            startLocalDate = startLocalDate.plusMonths(1);
        }
        result.add(shardingValue.getLogicTableName() + CommonContants.SPLIT + dateformat.format(startLocalDate));
        //获取实际表名
        Collection<String> result2 = new LinkedHashSet<String>();
        for (String availableTargetName : availableTargetNames) {
            for (String logicTable : result) {
                if (availableTargetName.contains(logicTable)) {
                    result2.add(availableTargetName);
                }
            }
        }
        return result2;
    }
}


相关文章
|
6月前
|
存储 Dubbo 算法
SpringCloud原理分析 | 简介
SpringCloud原理分析 | 简介
86 0
|
SQL 算法 搜索推荐
Mqsql使用Sharding-JDBC案例实战 2
Mqsql使用Sharding-JDBC案例实战
109 0
|
算法 Java 关系型数据库
Mqsql使用Sharding-JDBC案例实战 1
Mqsql使用Sharding-JDBC案例实战
47 0
|
6月前
|
Dubbo Java 应用服务中间件
Dubbo 第四节: Spring与Dubbo整合原理与源码分析
DubboConfigConfigurationRegistrar的主要作⽤就是对propties⽂件进⾏解析并根据不同的配置项项⽣成对应类型的Bean对象。
153 0
|
SQL 算法 Java
分库分表(4)——ShardingJDBC原理和源码分析
分库分表(4)——ShardingJDBC原理和源码分析
430 1
分库分表(4)——ShardingJDBC原理和源码分析
|
11月前
|
缓存 Java 程序员
spring技术内幕——深入解析spring架构与设计原理
序员与平庸的程序员之间的区别,是在于认为自己的代码重要还是数据结构更加重要。平庸的程序员眼里只有代码,优秀的程序员则关注数据结构及之前的关系。”
|
监控 中间件 关系型数据库
MyCAT、ShardingSphere和Mocc这三个中间件的优缺点对比
MyCAT、ShardingSphere和Mocc这三个中间件的优缺点对比
|
监控 负载均衡 Dubbo
面试官:谈谈 Spring Cloud 与 Dubbo 有什么区别?
面试官:谈谈 Spring Cloud 与 Dubbo 有什么区别?
284 0
面试官:谈谈 Spring Cloud 与 Dubbo 有什么区别?
|
关系型数据库
ShardingSphere 实战
ShardingSphere 实战
85 0
下一篇
无影云桌面