分库分表中间件-sharding-jdbc学习笔记-基于SpringBoot+TKMybatis搭建

一、前言

常用分库分表的框架或中间件有MyCat和Sharding-JDBC。MyCat是基于中间件的形式,shrrding-jdbc是基于本地jar包的类库。sharding-jdbc属于ShardingSphere体系的一个组件,ShardingSphere体系也有Sharding-Proxy,和MyCat一样,是独立部署的中间件。但sharding-jdbc与其不冲突,今天主要是学习了Sharding-JDBC。现将学习心得记录在这里,方便自己学习和他人参考(估计也不会有人看TAT)。

二、先动手简单写个小demo

(一)数据准备

这里用两个库,一张逻辑表,一个库两张真实表来演示。

sql脚本(基于mysql):

CREATE TABLE `t_order_1` (
  `id` bigint(20) NOT NULL,
  `user_id` bigint(20) NOT NULL,
  `order_id` bigint(20) NOT NULL,
  `order_no` varchar(30) NOT NULL,
  `isactive` tinyint(4) NOT NULL DEFAULT '1',
  `inserttime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updatetime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

每个库t_order_1和t_order_2各建一张。

(一)引入jar包

maven引入当前最新的jar包:

 
            org.apache.shardingsphere
            sharding-jdbc-core
            4.0.0-RC1

(三)编写真实数据源的方法

采取java配置方式,官方支持javaConfig、yml等配置方式,我这里用的是JavaConfig。

先将数据源信息写入yml:

sharding:
  datasources:
      test1:
        driver-class-name: com.mysql.jdbc.Driver
        password: 123456
        url: jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
        username: root
      test2:
        driver-class-name: com.mysql.jdbc.Driver
        password: 123456
        url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
        username: root

然后建个配置信息类,保存所有的数据源信息:

@Component
@ConfigurationProperties(prefix = "sharding")
@Data
public class DataSourceInfo {
    private Map> datasources;
}

对应的,创建真实数据源的方法,等会sharding-jdbc会用这些数据源封装为一个统一数据源入口。

public class DataSourceUtil {
    public static DataSource createDataSource(String data,DataSourceInfo dataSourceInfo){
        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.url(dataSourceInfo.getDatasources().get(data).get("url"));
        dataSourceBuilder.driverClassName(dataSourceInfo.getDatasources().get(data).get("driver-class-name"));
        dataSourceBuilder.username(dataSourceInfo.getDatasources().get(data).get("username"));
        dataSourceBuilder.password(dataSourceInfo.getDatasources().get(data).get("password"));
        DataSource dataSource = dataSourceBuilder.build();
        return dataSource;
    }
}

(四)配置sharding-JDBC的数据源

/**
 * @author chenzhicong
 * @time 2019/8/21 16:40
 * @description
 */
@Configuration
public class ShardingJdbcConfig {
    @Autowired
    private DataSourceInfo dataSourceInfo;
    @Bean(name = "shardingDataSource")
    @Primary
    DataSource getShardingDataSource() throws SQLException {
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        //添加t_order表逻辑表的分片规则配置对象
        shardingRuleConfig.getTableRuleConfigs().add(getOrderTableRuleConfiguration());
        //shardingRuleConfig.getTableRuleConfigs().add(getOrderItemTableRuleConfiguration());
        //添加绑定表
        //shardingRuleConfig.getBindingTableGroups().add("t_order, t_order_item");
        //添加广播表
        //shardingRuleConfig.getBroadcastTables().add("t_config");
        //设置分库策略
        //参数:第一个为分片列名称,第二个分片算法行表达式,需符合groovy语法
        shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(new InlineShardingStrategyConfiguration("user_id", "test${user_id % 2 + 1}"));
        //设置分表策略
        //这里一般不用行表达式分片策略,因为涉及的逻辑表名有多个,一般自己实现一个标准分片策略
        shardingRuleConfig.setDefaultTableShardingStrategyConfig(new InlineShardingStrategyConfiguration("order_id", "t_order_${order_id % 2}"));
        return ShardingDataSourceFactory.createDataSource(createDataSourceMap(), shardingRuleConfig, new Properties());
    }

    private static KeyGeneratorConfiguration getKeyGeneratorConfiguration() {
        KeyGeneratorConfiguration result = new KeyGeneratorConfiguration("SNOWFLAKE", "id");
        return result;
    }
    /**
      * 获取订单表分片规则配置对象
      */
    TableRuleConfiguration getOrderTableRuleConfiguration() {
        //第一个参数是逻辑表名称(logicTable),第二个参数是actualDataNodes(真实数据节点),由数据源名 + 表名组成,以小数点分隔(inline表达式)缺省表示使用已知数据源与逻辑表名称生成数据节点
        TableRuleConfiguration result = new TableRuleConfiguration("t_order", "test${1..2}.t_order${0..1}");

        result.setKeyGeneratorConfig(getKeyGeneratorConfiguration());
        return result;
    }
    /**
     * 获取订单商品表分片规则配置对象
     */
    TableRuleConfiguration getOrderItemTableRuleConfiguration() {
        TableRuleConfiguration result = new TableRuleConfiguration("t_order_item", "test${1..2}.t_order_item${0..1}");
        return result;
    }

    Map createDataSourceMap() {
        Map result = new HashMap<>();
        result.put("test1", DataSourceUtil.createDataSource("test1",dataSourceInfo));
        result.put("test2", DataSourceUtil.createDataSource("test2",dataSourceInfo));
        return result;
    }
}

(五)将统一的sharding-jdbc数据源引入mybatis

@Configuration
@MapperScan(basePackages = "com.czc.study.mybatis.dao", sqlSessionFactoryRef = "sessionFactory")
public class SessionFactoryConfig {
    @Bean
    public SqlSessionFactory sessionFactory(DataSource shardingDataSource) throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(shardingDataSource);
        return sessionFactory.getObject();
    }
}

注意了,这里的@MapperScan是tkMybatis的MapperScan注解(不要踩坑了)。

三、sharding-jdbc概念介绍

(一)名词解释

  • 数据节点 表示一张真实表。
  • 逻辑表 表示逻辑意义上的一张表,对于写业务代码的人来说所有sql都是操作的逻辑表
  • 绑定表 分片规则一致的主表和子表。配置时可以指定绑定表,sharding-jdbc将基于绑定表优化查询

(二)分片

分片有库分片和表分片,指的是我们数据在库和表间分布的策略。

sharding-jdbc提供多种分片策略,在配置时用了策略模式,需要传入特定算法来初始化,算法由我们自己实现,提供最大自由度。,包括:

  • 标准分片策略

对应的Java类对象是StandardShardingStrategy,可传入的算法对象是PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法,前者是精确分片算法,用于处理分片键in或=的查询关系,后增是范围分片算法,用于处理between and 的查询关系。在配置时,我们需要自己实现。比如精确分片算法的方法是:

String doSharding(Collection availableTargetNames, PreciseShardingValue shardingValue);

其中availableTargetNames代表的是可用的库或表,shardingValue封装的是分片键(携带逻辑表名,字段名、值),该方法实现需要返回库名或者表名,代表数据分布到哪个库或哪个表。后面介绍的算法对象都是类似这样的接口,就不多介绍了。

  • 复合分片策略

对应策略类对象ComplexShardingStrategy,提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持。对应算法对象ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂。这个也没有实际操作过,就不多介绍了。

  • 行表达式分片策略

对应InlineShardingStrategy策略类对象,该策略类初始化有传入分片键和Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。 我们demo中用的就是这个。

  • Hint分片策略

对应HintShardingStrategy。通过Hint而非SQL解析的方式分片的策略。对应算法对象HintShardingAlgorithm,主要用于分片字段非SQL决定,而由其他外置条件决定的场景。

(三)再来看看路由策略

路由策略指的是根据分片策略shrad-jdbc将把原始sql路由到哪些库执行。具体方式有:

  • 直接路由,通过Hint(使用HintAPI直接指定路由至库表)方式分片,并且是只分库不分表的前提下将进行直接路由
  • 标准路由,不包含关联查询或仅包含绑定表之间关联查询的SQL将进行标准路由,业务需求不变的话,效率是最优的。
  • 笛卡尔路由,没有绑定表关系,又有一些级联操作,会触发笛卡尔路由。

比如原始SQL是:

SELECT * FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id  WHERE order_id IN (1, 2);

如果有绑定键

SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id  WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id  WHERE order_id IN (1, 2);

但没有绑定键的话,不能确定order_id相等表的序号也相等,于是:

SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id  WHERE order_id IN (1, 2);
SELECT * FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id  WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id  WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id  WHERE order_id IN (1, 2);
  • 广播路由,不携带分片键的SQL,将进行广播路由,广播路由分为全库表路由、全实例路由、单播路由、阻断路由,我们只需要了解全库表路由即可,其他的SQL都不属于crud,全库表路由值得是针对一个SQL,sharding-jdbc会在所有库都执行一次。

  • 图示:

分库分表中间件-sharding-jdbc学习笔记-基于SpringBoot+TKMybatis搭建_第1张图片

(四)SQL改写相关功能

针对,逻辑SQL(表示业务代码中写的sql),某些情况sharding-jdbc将会对其进行改写,主要情况有:

  • 标识符改写,指的是将逻辑sql中的表名改为真实表名。
  • 补列,主要是针对order by和GROUP BY,归并结果之前必须获取分组字段或排序字段,所以改写后的sql查询结果会有分组或排序字段
  • 分页修正,对于排序分页,会改写为Limit总是从0开始,获取正确的排序结果,再归并筛选。
  • 批量拆分,批量插入时拆不同的SQL到不同的库执行,或In查询时,根据分片策略针对每个库的分片键范围,缩小IN查询条件的范围。

需要注意的是,对于单节点路由,则不会进行改写。

(五)关于归并

对于排序、分组排序的归并不一定将查询结果搞到内存,然后在内存中进行归并,sharding-jdbc有流式分组归并的概念,即每一次从结果集中获取到的数据,都能够通过逐条获取的方式返回正确的单条数据。但对于分组排序的要求必须是SQL的排序项与分组项的字段以及排序类型(ASC或DESC)。

四、小结

关于sharding-jdbc的核心概念和配置demo就总结到这里,大概对其有了一定了解,再总结一下配置的方式首先是先获取到各个真实数据源的datasource对象,然后创建分片规则配置对象ShardingRuleConfiguration,通过分片规则配置对象添加各个逻辑表的分片规则配置对象TableRuleConfiguration(这里可以配置各个TableRuleConfiguration的主键生成策略,可以选择使用用雪花算法),然后添加各个绑定表组,通过传入自己实现的算法配置分片策略(这里需要注意,最好每个表都有相同的字段作为分片键,这样比较好处理-个人看法,可能有可能不对,因为也没有经历过生产实践),然后再通过ShardingDataSourceFactory结合真实数据源创建统一的ShardingDataSource,最后注入到我们自己的持久层框架使用的数据源中就行了。

转载于:https://my.oschina.net/u/4101481/blog/3095861

你可能感兴趣的:(分库分表中间件-sharding-jdbc学习笔记-基于SpringBoot+TKMybatis搭建)