Spring Boot 集成 Sharding JDBC 分库分表

Apache ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar 这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。它们均提供标准化的数据水平扩展、分布式事务和分布式治理等功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。

ShardingSphere 是一个很活跃的项目,当前稳定版是 4.x ,预览版 5.x 及文档早已发布。ShardingSphere 早期 3.x 之前版本和 4.x 之后版本配置不兼容,而且从 4.x 开始才修复解析 select for update 语句问题,并且严格校验 schema ,不允许跨库查询。

本章基于 4.x 版本,使用 Sharding JDBC 实现分库分表,并配置 Sharding Proxy 和 Sharding UI 实现聚合查询。

Sharding JDBC

Sharding JDBC 定位为轻量级Java框架,在Java的JDBC层提供的额外服务。它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。

  • 引入依赖

Sharding JDBC:

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

Spring JDBC 和 MySQL 驱动:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
  • 配置

配置数据源

spring:
  shardingsphere:
    props:
      show-sql: true # 打印sql语句,调试时可开启
    datasource:
      names: master1,slave1,slave2 # 配置三个数据源,主库master1,从库slave1和slave2
      master1:
        type: org.apache.commons.dbcp2.BasicDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://${MYSQL_HOST:localhost}:3307/engrz?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
        username: ${MYSQL_USER:engrz}
        password: ${MYSQL_PASSWORD:engrz2021}
      slave1:
        type: org.apache.commons.dbcp2.BasicDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://${MYSQL_HOST:localhost}:3308/engrz?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
        username: ${MYSQL_USER:engrz}
        password: ${MYSQL_PASSWORD:engrz2021}
      slave2:
        type: org.apache.commons.dbcp2.BasicDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://${MYSQL_HOST:localhost}:3309/engrz?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
        username: ${MYSQL_USER:engrz}
        password: ${MYSQL_PASSWORD:engrz2021}

读写分离和分库分表

spring:
  shardingsphere:
    sharding:
      master-slave-rules:
        ds1: # 读写分离数据源,如果有多个库可配置多个
          master-data-source-name: master1
          slave-data-source-names: slave1,slave2
      tables:
        t_user_info: # 表名
          actual-data-nodes: ds1.t_user_info_${0..9} # 规则,使用Groovy语法
          table-strategy:
            inline:
              sharding-column: user_id # 分片字段
              algorithm-expression: t_user_info_${user_id % 10}
        t_user_log: # 表名
          actual-data-nodes: ds1.t_user_log_$->{2019..2021} # 规则,使用Groovy语法
          table-strategy:
            standard:
              sharding-column: log_date # 分片字段
              precise-algorithm-class-name: com.engrz.commons.sharding.jdbc.algorithm.DatePreciseModuloShardingTableAlgorithm
              range-algorithm-class-name: com.engrz.commons.sharding.jdbc.algorithm.DateRangeModuloShardingTableAlgorithm

Sharding JDBC 配置单纯的读写分离和分库分表的读写分离配置写法有一点差别
仅使用读写分离配置属性:spring.shardingsphere.masterslave.*
分库分表的读写分离配置属性:spring.shardingsphere.sharding.master-slave-rules.*

配置文件中分片说明:

t_user (用户表)

分10张表,user_id字段为long型主键,使用 user_id 值取模,把计算结果拼上表名,完整名如:t_user_info_0、t_user_info_1

t_user_log (用户日志表)

按年份分表,从2019到2021,使用自定义分片策略

DatePreciseModuloShardingTableAlgorithm:

/**
 * 日期精确匹配
 */
public class DatePreciseModuloShardingTableAlgorithm implements PreciseShardingAlgorithm<Date> {

    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Date> preciseShardingValue) {

        Date date = preciseShardingValue.getValue();
        Calendar c = Calendar.getInstance();
        c.setTime(date);
        String year = String.valueOf(c.get(Calendar.YEAR));

        String tableName = null;
        for (String tmp : collection) {
            if (tmp.endsWith(year)) {
                // 如果以当前年份结尾
                tableName = tmp;
                break;
            }
        }

        if (null == tableName) {
            String str = collection.iterator().next();
            tableName = str.substring(0, str.lastIndexOf("_"));
        }

        return tableName;
    }
}

DateRangeModuloShardingTableAlgorithm:

/**
 * 日期范围查询
 */
public class DateRangeModuloShardingTableAlgorithm implements RangeShardingAlgorithm<Date> {

    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Date> rangeShardingValue) {

        // 这里可以处理日期字段,避免多余查询
        Range<Date> range = rangeShardingValue.getValueRange();

        Integer start = null;
        if (range.hasLowerBound()) {
            Date lowerEndpoint = range.lowerEndpoint();
            Calendar c = Calendar.getInstance();
            c.setTime(lowerEndpoint);
            start = Calendar.YEAR;
        }
        Integer end = null;
        if (range.hasUpperBound()) {
            Date upperEndpoint = range.upperEndpoint();
            Calendar c = Calendar.getInstance();
            c.setTime(upperEndpoint);
            end = Calendar.YEAR;
        }

        if (null == start && null == end) {
            return collection;
        }

        List<String> list = new ArrayList<>();
        for (String tableName : collection) {
            int suffix = Integer.parseInt(tableName.substring(tableName.lastIndexOf("_") + 1));
            if (null != start && suffix < start) {
                continue;
            }
            if (null != end && suffix > end) {
                continue;
            }
            list.add(tableName);
        }

        return list;
    }
}

如果日志量大,还可以按季度分表,只要在方法中稍作修改,返回对应的表名即可。

关于 Sharding JDBC 的分片策略和分片算法,可查阅官方文档。

  • 注意事项
  1. 使用分库分表后要注意主键唯一,可使用雪花算法生成主键
  2. 使用 Sharding JDBC 查询时尽量带上分库分表字段作为条件,避免全局扫描
  3. 主从模型中,事务中读写均用主库
  4. 某些sql语句不支持解析,如 distinct 和 group by 连用
  • Sharding JDBC 4.x SQL 解析支持说明

Sharding Proxy

Sharding Proxy 定位为透明化的数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。 目前先提供MySQL/PostgreSQL版本,它可以使用任何兼容MySQL/PostgreSQL协议的访问客户端(如:MySQL Command Client, MySQL Workbench, Navicat等)操作数据,对DBA更加友好。

使用 Sharding JDBC 分库分表后,数库落在不同的数据库和数据表中,使用 Sharding Proxy 作为代理,它会帮我们把上面的 t_user_0,t_user_1 … 聚合成一张 t_user 逻辑表。

Sharding Proxy 的安装配置请参考官方文档。如果有自定义分片算法,把代码打成JAR包放到 Sharding Proxy 解压后的conf/lib目录下。

实际使用中发现 Sharding Proxy 对连接的客户端有版本校验,比如我用 MySQL Workbench 8.0 无法连接 MySQL 5.7 的数据库。可以使用 DBeaver 客户端,手动指定与服务端版本对应的驱动。

Sharding UI

Sharding UI是 ShardingSphere 的一个简单而有用地web管理控制台。它用于帮助用户更简单的使用 ShardingSphere 的相关功能,目前提供注册中心管理、动态配置管理、数据库编排等功能。

Sharding UI 和 Sharding Proxy 方便我们管理数据库,在理解 Sharding JDBC 分库分表后配置十分简单,可参考 Sharding Sphere 4.x 相关文档


除非注明,否则均为"攻城狮·正"原创文章,请注明出处。

本文链接:https://engr-z.com/177.html

发表评论

您的电子邮箱地址不会被公开。