Spring Boot 集成 Sharding JDBC 分库分表

2,168 阅读4分钟

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

0e7c8999700f2edaf224318d212d18ce.png

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 解析支持说明

837c4a97636fc7c42d9f70326e67c990.png

02818efd8f6eb0a78ec9a6c734847fe7.png

98f77ad1c51be06bde2d92475376f3bc.png

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 相关文档


除非注明,否则均为"攻城狮·正"原创文章,转载请注明出处。 本文链接:engr-z.com/177.html