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;
    }
}


目录
相关文章
Echarts2.0雷达图tooltip单轴数据
Echarts2.0雷达图tooltip单轴数据
418 0
Unable to interpret the implicit parameter configuration with dataType: , dataTypeClass: class java.
Unable to interpret the implicit parameter configuration with dataType: , dataTypeClass: class java.
1008 0
|
存储 小程序 前端开发
【微信小程序 - 工作实战分享】1.微信小程序发送手机短信验证码(阿里云)
【微信小程序 - 工作实战分享】1.微信小程序发送手机短信验证码(阿里云)
1243 0
|
关系型数据库 MySQL Java
ShardingSphere 实战之读写分离
采用 ShardingShpere 的 Sharding-Porxy(透明化的数据库代理端) 模式在本地实现 mysql 数据库读写分离,并通过 java 应用程序连接.
ShardingSphere 实战之读写分离
|
程序员 开发者 Python
探索Python中的装饰器:从基础到高级应用
本文旨在全面解析Python中一个强大而灵活的特性——装饰器(Decorators)。我们将从装饰器的基本定义出发,逐步深入到它们的高级应用。通过具体的代码示例和详细的解释,读者将能够掌握如何有效地使用装饰器来增强函数和类的功能,以及如何创建自定义装饰器来解决特定问题。无论是Python初学者还是经验丰富的开发者,都能在本文中找到有价值的内容,以提升编程技巧和代码质量。
361 1
|
JavaScript Java 关系型数据库
基于springboot+vue在线外卖点餐系统(毕业设计,源码+文档)
基于springboot+vue在线外卖点餐系统(毕业设计,源码+文档)
|
开发框架 前端开发 Android开发
【Flutter 前端技术开发专栏】Flutter 与原生模块通信机制
【4月更文挑战第30天】本文探讨了Flutter作为跨平台开发框架与原生Android和iOS交互的必要性,主要通过方法调用和事件传递实现。文中详细介绍了Flutter与Android/iOS的通信方式,数据传输(包括基本和复杂类型),性能优化,错误处理以及实际应用案例。理解并掌握这一通信机制对开发高质量移动应用至关重要,未来有望随着技术发展得到进一步优化。
311 0
【Flutter 前端技术开发专栏】Flutter 与原生模块通信机制
|
SQL 缓存 关系型数据库
myCat 9066管理端口常用命令
myCat 9066管理端口常用命令
188 0
|
SQL 存储
若依框架---PageHelper分页(十五)
若依框架---PageHelper分页(十五)
361 0
|
JSON 算法 安全
Spring Boot 如何使用 JWT 进行认证和授权
Spring Boot 如何使用 JWT 进行认证和授权