Spring Boot 集成 Sharding JDBC 分库分表

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

sharding-sphere.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 解析支持说明
20210202162541.jpg
20210202162551.jpg
20210202162557.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 相关文档


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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容