使用 ShardingSphere+MybatisPlus 实现分库分表以及常见问题
本文最后更新于 101 天前,其中的信息可能已经有所发展或是发生改变。

当数据库的压力和数据表的数据量过大时需要考虑分库分表问题,但是传统的 Spring+Mybatis 的分库分表实现有些复杂,于是可以使用 ShardingSphere 来解决。

Apache ShardingSphere 是一款分布式的数据库生态系统, 可以将任意数据库转换为分布式数据库,并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。

Apache ShardingSphere 设计哲学为 Database Plus,旨在构建异构数据库上层的标准和生态。 它关注如何充分合理地利用数据库的计算和存储能力,而并非实现一个全新的数据库。 它站在数据库的上层视角,关注它们之间的协作多于数据库自身。

这里是 ShardingSphere 的官网:https://shardingsphere.apache.org/

前提

现在我有一张 users 数据表,将它水平拆分成 users_0users_1,主键为 id BIGINT

依赖引入

警告:如果你打算参考本篇文章实现分库分表,请严格按照我的依赖版本引入必要的依赖。
runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'org.apache.shardingsphere:shardingsphere-jdbc-core-spring-boot-starter:5.0.0'
implementation 'com.baomidou:mybatis-plus-spring-boot3-starter:3.5.7'
implementation 'com.baomidou:dynamic-datasource-spring-boot-starter:3.3.2'
implementation 'org.antlr:antlr4-runtime:4.9.2'

这里注意一下最后一个 org.antlr:antlr4-runtime 依赖,具体版本根据你实际修改,怎么修改见文末的常见问题。

配置文件

首先在 application.yml 中添加配置。

添加数据源

由于 MybatisPlus 需要一个默认的数据源 master,方便起见,第一个数据源就命名为 master。
spring:
  application:
    name: sharding-sphere-test
  shardingsphere:
    props:
      sql-show: true
    datasource:
      names: master
      master:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbcUrl: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${TABLE_NAME}?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimeZone=UTC
        url: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${TABLE_NAME}?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimeZone=UTC
        username: ${MYSQL_USERNAME}
        password: ${MYSQL_PASSWORD}

ShardingSphere 支持多个数据源,因此你可以在 datasource 下添加多个自定义的数据源,然后在 names 中以英文逗号分割名称即可。

例如:

spring:
  application:
    name: sharding-sphere-test
  shardingsphere:
    props:
      sql-show: true
    datasource:
      names: master,ds2
      master:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbcUrl: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${TABLE_NAME}?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimeZone=UTC
        url: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${TABLE_NAME}?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimeZone=UTC
        username: ${MYSQL_USERNAME}
        password: ${MYSQL_PASSWORD}
      ds2:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbcUrl: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${TABLE_NAME}?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimeZone=UTC
        url: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${TABLE_NAME}?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimeZone=UTC
        username: ${MYSQL_USERNAME}
        password: ${MYSQL_PASSWORD}

分表规则

接着上面的配置继续往下写,接下来是分表的规则。

也就是说,你可以通过 id % SHARD_COUNT 或其他方式来进行分表,而 ShardingSphere 已经提供了一些常用的分表方式,如下:

spring:
  application:
    name: sharding-sphere-test
  shardingsphere:
    props:
      sql-show: true
    datasource:
      names: master
      master:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbcUrl: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${TABLE_NAME}?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimeZone=UTC
        url: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${TABLE_NAME}?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimeZone=UTC
        username: ${MYSQL_USERNAME}
        password: ${MYSQL_PASSWORD}
# 配置分表规则
    rules:
      sharding:
        sharding-algorithms:
          user-database-sharding-strategy:
            type: INLINE
            props:
              algorithm-expression: master
          user-table-sharding-strategy:
            type: INLINE
            props:
              algorithm-expression: users_$->{id % 2}

这里创建了两个分表规则,一个是 user-database-sharding-strategy,另一个是 user-table-sharding-strategy,名字可以自定义。

解释一下,这里的 algorithm-expression 就代表 SQL 语句中的实际数据表的名字,而这个 Groovy 表达式中的 id 就是你数据表的字段。

例如最新插入的 用户 id 是 114514,那么经过这个表达式,得到的最终表名就是 users_0

如果想了解更多分片算法,请见:分片算法 :: ShardingSphere (apache.org)

定义数据表

有了分表规则,下面就要定义有哪些数据表要用哪些规则了,接着上面的配置继续写:

spring:
  application:
    name: sharding-sphere-test
  shardingsphere:
    props:
      sql-show: true
    datasource:
      names: master
      master:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbcUrl: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${TABLE_NAME}?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimeZone=UTC
        url: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${TABLE_NAME}?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimeZone=UTC
        username: ${MYSQL_USERNAME}
        password: ${MYSQL_PASSWORD}
# 配置分表规则
    rules:
      sharding:
        sharding-algorithms:
          user-database-sharding-strategy:
            type: INLINE
            props:
              algorithm-expression: master
          user-table-sharding-strategy:
            type: INLINE
            props:
              algorithm-expression: users_$->{id % 2}
# 配置数据表
        tables:
          users:
            actual-data-nodes: master.users_$->{0..1}
            database-strategy:
              standard:
                sharding-column: id
                sharding-algorithm-name: user-database-sharding-strategy
            table-strategy:
              standard:
                sharding-column: id
                sharding-algorithm-name: user-table-sharding-strategy
            key-generate-strategy:
              column: id
              key-generator-name: my-snowflake
        key-generators:
          my-snowflake:
            type: SNOWFLAKE

actual-data-nodes 则是实际的 SQL,ShardingSphere 不仅可以分表还可以分库,但是这里我只有一个 master 数据源,所以分库就省略了,直接 master 即可。

我这里写的 master.users_$->{0..1} 就代表在 master 数据源中,有 users_0users_1 两张表是可用的。注意一下,如果你上面的分片不止两个,这里的值也要随之变化,否则会报数据表不可用的异常。

如果你有多个数据库,你还可以用 Groovy 表达式来替代 master,例如 db$->{0..2}.users_$->{0..1},就对应着 db0, db1, db2 三个数据源。

除了这种方式,你还可以用逗号分割,例如 `db0.users_0,db0.users_1,db1.users_0,db1.users_1,db2.users_0,db2.users_1`,这种方式有些麻烦。

这里的 sharding-column 就是分片规则的目标列了,而 sharding-algorithm-name 就是上面自定义的分表规则。

key-generators 就是 ShardingSphere 为你提供的或自定义的 id 生成规则了,插入数据时如果你指定 id 字段为空,那么 ShardingSphere 就会按照这个规则给你生成一个 id,这里我用的是雪花算法。

配置类

创建一个 DataSourceConfig 类:

@Configuration
@AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class, SpringBootConfiguration.class})
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
public class DataSourceConfig {
    @Resource
    private DynamicDataSourceProperties dynamicDataSourceProperties;

    @Lazy
    @Resource
    private AbstractDataSourceAdapter shardingSphereDataSource;

    @Value("${spring.shardingsphere.datasource.names}")
    private String dataSourceNames;

}

ShardingSphere 配置完成后,还要让 MybatisPlus 知道要用什么数据源,接下来就为 MP 添加一些数据源。

@Configuration
@AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class, SpringBootConfiguration.class})
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
public class DataSourceConfig {
    @Resource
    private DynamicDataSourceProperties dynamicDataSourceProperties;

    @Lazy
    @Resource
    private AbstractDataSourceAdapter shardingSphereDataSource;

    @Value("${spring.shardingsphere.datasource.names}")
    private String dataSourceNames;

    @Primary
    @Bean
    @ConditionalOnMissingBean
    public DefaultDataSourceCreator defaultDataSourceCreator(List<DataSourceCreator> dataSourceCreators) {
        DefaultDataSourceCreator defaultDataSourceCreator = new DefaultDataSourceCreator();
        defaultDataSourceCreator.setCreators(dataSourceCreators);
        defaultDataSourceCreator.setProperties(dynamicDataSourceProperties);
        return defaultDataSourceCreator;
    }

    @Bean
    public DynamicDataSourceProvider dynamicDataSourceProvider() {
        String[] names = dataSourceNames.split(",");
        return new AbstractDataSourceProvider() {
            @Override
            public Map<String, DataSource> loadDataSources() {
                Map<String, DataSource> dataSourceMap = new HashMap<>();
                Arrays.stream(names).forEach(name -> dataSourceMap.put(name, shardingSphereDataSource));
                return dataSourceMap;
            }
        };
    }

    @Primary
    @Bean
    public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        dataSource.setPrimary(dynamicDataSourceProperties.getPrimary());
        dataSource.setStrict(dynamicDataSourceProperties.getStrict());
        dataSource.setStrategy(dynamicDataSourceProperties.getStrategy());
        dataSource.setProvider(dynamicDataSourceProvider);
        dataSource.setP6spy(dynamicDataSourceProperties.getP6spy());
        dataSource.setSeata(dynamicDataSourceProperties.getSeata());
        return dataSource;
    }
}

dynamicDataSourceProvider() 中,把所有 ShardingSphere 中的数据源都添加给了 MP。再次注意,由于 MybatisPlusDynamic 需要一个默认的数据源 master,确保这个 Map 中有以 master 为 key 的数据源。

测试

配置到这里差不多就结束了,下面我们插入一条数据试试看。

直接用 MP 提供的方法插入即可:userService.getBaseMapper().insert(user);

查看控制台的日志如下所示:

ShardingSphere-SQL: Logic SQL: INSERT INTO users  ( id, username, password, email, nickname, active, registered_time )  VALUES (  ?, ?, ?, ?, ?, ?, ?  )
ShardingSphere-SQL: SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
ShardingSphere-SQL: Actual SQL: master ::: INSERT INTO users_1  ( id, username, password, email, nickname, active, registered_time )  VALUES (?, ?, ?, ?, ?, ?, ?) ::: [...]

查询也是一样的,这里就不再演示了,预期的结果应该是直接查询所有分片找出符合条件的记录。

常见问题

① No qualifying bean of type ‘org.apache.shardingsphere.driver.jdbc.adapter.AbstractDataSourceAdapter’ available

找不到 AbstractDataSourceAdapter 的实现类,按理来说 ShardingSphere 应该为我们提供了,如果找不到,在 @SpringBootApplication 添加包扫描。

@SpringBootApplication(scanBasePackages = {"your.package", "org.apache.shardingsphere"})

如果还是无效,请检查你的配置文件中的缩进是否正确。

rules 是4格缩进,tables 是8格缩进。sharding-algorithmstableskey-generators 同层级。

如果你使用的是 IDEA,这些配置项可以被正确识别并出现在补全提示中。

② org.yaml.snakeyaml.representer.Representer: method ‘void ()’ not found

这个纯属 ShardingSphere-Starter 的依赖问题,有两种解决办法。

  • 方法一:排除 shardingsphere-jdbc-core-spring-boot-starter 中的 org.yaml:snakeyaml 依赖,并且引入一个新的,包含 Representer() 无参构造函数的 snakeyaml
  • 方法二:和我一起使用 shardingsphere-jdbc-core-spring-boot-starter:5.0.0 版本不就好了嘛?

③ org.antlr.v4.runtime.atn.ATN; Could not deserialize ATN with version 3 (expected 4).

因为 ShardingSphere 使用了 Antlr 来解析 SQL 语句,如果你当前的版本不一致的话,必须改成和它一样的版本。

具体异常如下所示:

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3ed6d7fb] was not registered for synchronization because synchronization is not active
JDBC Connection [org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConnection@a59819f] will not be managed by Spring
==>  Preparing: INSERT INTO users ( id, username, password, email, nickname, active, registered_time ) VALUES (?, ?, ?, ?, ?, ?, ? )
ANTLR Tool version 4.9.2 used for code generation does not match the current runtime version 4.13.1
ANTLR Runtime version 4.9.2 used for parser compilation does not match the current runtime version 4.13.1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3ed6d7fb]

注意到这行 ANTLR Tool version 4.9.2 used for code generation does not match the current runtime version 4.13.1,说明我当前的版本太高了,在依赖中引入对应的版本即可:implementation 'org.antlr:antlr4-runtime:4.9.2'

④ 自定义ID生成器不生效

由于现在我正在使用MybatisPlus提供的 insert() 方法,这个方法会自动生成一条 INSERT INTO 语句,但是这条语句里面也包括了 id 字段,这就会导致 ShardingSphere 无法用它的 ID 生成器生成 ID。

在 GitHub 上搜到了相关问题,但是好像没有答案。mybatis-plus + 5.1.2,KeyGenerateAlgorithm can not be use · Issue #18721 · apache/shardingsphere (github.com)

解决办法:用 Mybatis 原生的 @Insert 自己写一条插入语句替代 MP 的 insert(),有其他方法亦可。

未经允许禁止转载本站内容,经允许转载后请严格遵守CC-BY-NC-ND知识共享协议4.0,代码部分则采用GPL v3.0协议
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇