掘金 后端 ( ) • 2024-04-25 10:32

写在文章开头

Sharding-JDBC是一款比较轻量级的分库分表框架,它使用客户端直连数据库,通过jar包形式提供服务,通过对数据源进行增强实现分库分表逻辑实现,同时它也很市面上的各种ORM框架完全兼容,所以就会以一个简单的用户查询的给出Sharding-JDBC基础入门教程。

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

Sharding-JDBC基础案例演示

前置步骤

Sharding-JDBC底层通过封装MySQL默认的方式实现分表逻辑,所以对于市面上主流的ORM框架都是支持的,在使用之前引入Sharding-JDBC的依赖:

  <!-- Sharding-JDBC -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>

然后我们再给出本次的分表的DDL语句,我们以user_0为例对应的语句如下所示,另外的user_1user_2结构是一致的这里就不多赘述了:

CREATE TABLE `user_0` (
  `id` bigint NOT NULL,
  `name` varchar(20) NOT NULL,
  `food_id` bigint NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

配置数据源

Sharding-JDBC通过对原始的数据源进行封装从而拓展出分库分表的逻辑,所以使用时也需要配置Sharding-JDBC的数据源信息,可以看到除了必要的账号密码以外,还有就是分库分表算法了,以笔者本次的示例来说,仅需要分表算法,所以仅仅通过通过table-strategy配置完成对id的分表取模算法:

#数据源名称(随便起名,如果有多个库,逗号隔开 ds0,ds1,ds2)
spring.shardingsphere.datasource.names=ds0
# 数据源
spring.shardingsphere.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds0.url=jdbc:mysql://localhost:3306/db?characterEncoding=utf-8
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=xxxx
#分表策略配置
##表名枚举,其中的user是需要分表的表名;ds0.user_$->{0..2} 其中ds0表示数据源名称;user_$->{0..2} 表示从user_0到user_2
spring.shardingsphere.sharding.tables.user.actual-data-nodes=ds0.user_$->{0..2}
##使用哪一列用作计算分表策略,我们就使用id
spring.shardingsphere.sharding.tables.user.table-strategy.inline.sharding-column=id
##具体的分表路由策略,我们有3个user表,使用主键id取余3,余数0/1/2分表对应表user_0,user_2,user_2
spring.shardingsphere.sharding.tables.user.table-strategy.inline.algorithm-expression=user_$->{id % 3}
##配置主键生成策略,因为多张表了,id不能在配置数据库自增,需要配置主键生成策略,user表主键名称是id
spring.shardingsphere.sharding.tables.user.key-generator.column=id
##id使用雪花算法,因为雪花算法生成的id具有全球唯一性,并且又有自增特性,适合mysql的innodb引擎
spring.shardingsphere.sharding.tables.user.key-generator.type=SNOWFLAKE
# 打开sql输出日志
spring.shardingsphere.props.sql.show=true

分表的CRUD操作

基于上述配置Spring boot就会完成Sharding-JDBC数据源的装配从而完成分表工具的加载,这里我们先给出一段插入示例,我们设置id为1,按照分表算法,这条数据最终会落user_1上:

  @Test
    void insert() {

        User user = new User();
        user.setId(1L);
        user.setName("user" + 1);
        user.setFoodId(0L);
        userMapper.insert(user);
    }

最终我们从打印日志印证了这一点,这条数据确实插入到user_1 表:

2024-04-24 23:53:43.266  INFO 25532 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds0 ::: insert into user_1  (id, name, food_id) VALUES (?, ?, ?) ::: [1, user1, 0]

同理我们进行查询id为1的操作:

 @Test
    void selectByExample() {

        UserExample userExample = new UserExample();
        userExample.createCriteria().andIdEqualTo(1L);
        log.info("query user:{}", userMapper.selectByExample(userExample).stream().findFirst().orElse(null));

    }

从输出的日志来看,还是定位到了user_1表,并成功查询到了数据:

2024-04-24 23:56:27.050  INFO 15480 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds0 ::: select
     
     
    id, `name`, food_id
   
    from user_1
     
       
     WHERE (  id = ? ) ::: [1]
2024-04-24 23:56:27.068  INFO 15480 --- [           main] com.sharkChili.mapper.UserMapperTest     : query user:com.sharkChili.domain.User@25218a4d

详解Sharding-JDBC工作原理

Sharding-JDBC的脚手架本质上就是基于Spring boot的自动装配原理,将我们配置的分库分表数据源、算法等信息生成ShardingDataSource,当我们基于Mybatis等框架进行CRUD操作时,ShardingDataSource就会返回它所实现的ShardingConnection,通过ShardingConnectionPreparedStatementexecute方法获取当前的SQL,并基于我们配置的算法生成新的SQL语句并提交到数据库执行从而得到结果:

回到源码,在Sharding-JDBC的脚手架中我们定位到spring.factories所要加载的SpringBootConfiguration,可以看到它基于配置生成ShardingDataSource的逻辑:

   @Bean
    public DataSource dataSource() throws SQLException {
       //......
       //基于配置中加载的信息所得到的dataSourceMap、分表策略配置信息shardingProperties生成ShardingDataSource
        return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingSwapper.swap(shardingProperties), propMapProperties.getProps());
    }

以查询操作为例,代码会从Mybatis的代理的执行器SimpleExecutor走到doQuery方法,它会通过prepareStatement方法拿到ShardingConnection对应的prepareStatement,然后通过query进行查询:

@Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //通过`prepareStatement`方法拿到`ShardingConnection`对应的`prepareStatement`
      stmt = prepareStatement(handler, ms.getStatementLog());
      //执行分库分表查询
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

shard方法内部会走到StandardRoutingEnginerouteTables方法,该方法通过我们配置的tables.user.table-strategy.inline.algorithm-expression=user_$->{id % 3}得到一个InlineShardingStrategy,而InlineShardingStrategy策略内部会使用我们的id % 3生成Closure对象,而的doCall方法的逻辑就是id % 3,这也就意味着我们传入的id实际上就是通过InlineShardingStrategy内部生成的Closure得到分表结果:

对应我们的也给出对应的核心代码示例,自此我们就将Sharding-JDBC的核心逻辑剖析完毕了:


private Collection<DataNode> routeTables(final TableRule tableRule, final String routedDataSource, final List<RouteValue> tableShardingValues) {
        Collection<String> availableTargetTables = tableRule.getActualTableNames(routedDataSource);
        //通过shardingRule.getTableShardingStrategy获取分表策略并调用doSharding得到结果表
        Collection<String> routedTables = new LinkedHashSet<>(tableShardingValues.isEmpty() ? availableTargetTables
                : shardingRule.getTableShardingStrategy(tableRule).doSharding(availableTargetTables, tableShardingValues));
        Preconditions.checkState(!routedTables.isEmpty(), "no table route info");
        Collection<DataNode> result = new LinkedList<>();
        for (String each : routedTables) {
            result.add(new DataNode(routedDataSource, each));
        }
        return result;
    }

小结

以上便是笔者对于Sharding-JDBC分库分表算法配置与实现的剖析,后续我们还会对与分页查询、自定义分库分表算法的实现进行着重介绍,希望对你有帮助,感谢您的阅读。

我是 sharkchiliCSDN Java 领域博客专家开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。 因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

参考

SPRING BOOT配置:https://shardingsphere.apache.org/document/4.1.1/cn/manual/sharding-jdbc/configuration/config-spring-boot/

分库分表神器sharding-jdbc在springboot中的全场景使用demo:https://blog.csdn.net/csdn_20150804/article/details/118252213

本文使用 markdown.com.cn 排版