ShardingSphere入门篇

通过ShardingSphere实现分库分表

本章基于项目演示分库分表的具体实现

image

分库分表的概述

分库是将原本的单库拆分为多个库,分表是将原来的单表拆分为多个表。
image

分库有两种模式:

  • 垂直拆库:电商库MallDB,业务拆分后就是UserDB、OrderDB、PayDB等。
  • 分片拆库:用户库UserDB,分片库后就是UserDB_0、UserDB_1、UserDB_xx。

分表也有两种模式:

  • 垂直拆表: 订单表 OrderTable,拆分后就是 OrderTable 以及 OrderExtTable。
  • 水平拆表: 订单表 OrderTable,拆分后就是 OrderTable_0、 OrderTable_xxx。

📣什么场景分库分表?

当出现以下两种场景下,需要进行分库又分表:高并发写入和海量数据:

高并发写入场景:当应用面临高并发的写入请求时,单一数据库可能无法满足写入压力,此时可以将数据按照一定规则拆分到多个数据库中,每个数据库处理部分数据的写入请求,从而提高写入性能。

海量数据场景:随着数据量的不断增加,单一数据库的存储和查询性能可能逐渐下降。此时,可以将数据按照一定的规则拆分到多个表中,每个表存储部分数据,从而分散数据的存储压力,提高查询性能。

分库分表设计

如何选择分片键?

  • 数据均匀性: 分片键应该保证数据的均匀分布在各个分片上,避免出现热点数据集中在某个分片上的情况。
  • 业务关联性: 分片键应该与业务关联紧密,这样可以避免跨分片查询和跨库事务的复杂性。
  • 数据不可变: 一旦选择了分片键,它应该是不可变的,不能随着业务的变化而频繁修改。

分库分表算法?

分库分表的算法会根据业务的不同而变化,所以并没有固定算法。在业界里用的比较多的有两种:

  • HashMod:通过对分片键进行哈希取模的分片算法(本次所使用)
  • 时间范围:基于时间范围分片算法

📣优惠券模板如何分库?

1.优惠券模板分多少表?

根据上面数据估算,30亿数据量需要分多少个表?这其实又会涉及到一个知识点,那就是SQL复杂么?

  • SQL复杂,拆分百万级别。
  • SQL不复杂,全部走索引,千万甚至亿级别。

我们以优惠券模板举例,不涉及复杂 SQL,但是依然不建议大家数据量到达亿级别,总归要留有余量。在这里我们取经验值 2000 万,30 亿数据就是拆分 150 张表即可。

为什么取2000万?其实数据量不是特别多的情况下,基本上3次磁盘IO就能获取到数据。再多的话可能磁盘IO会增加,但是还好。考虑到数据库表备份等其他操作,不建议单表太多数据。

2.优惠券模板是否需要分库?

不需要,因为并发不高。

如果需要分析一个业务场景如何分库,那就需要知道单个MySQL Server的瓶颈是多少?通过之前压测得知,单台MySQL Server的写瓶颈大概在 4000-5000 /TPS,查询可能更高一些。

如果我们的场景业务每秒TPS在1万,那么就需要至少分两个库,然后将上面的150张表分别放入即可。

后面优惠券的分发和领取是需要分库的,所以我们为了不重复创建表,在这里直接通过分库的形式展示。

3.优惠券模板表分片键如何选择?

参考国内某电商平台优惠券管理页面,需要根据当前店铺创建的优惠券分页查询。

答案呼之欲出,那就是店铺编号字段
image

ShardingSphere 项目实战

1.初始化数据库&表

创建两个数据库:

CREATE DATABASE IF NOT EXISTS one_coupon_rebuild_0;
CREATE DATABASE IF NOT EXISTS one_coupon_rebuild_1;

分别在两个数据库中创建优惠券模板表:

CREATE TABLE `t_coupon_template_0`
(
    `id`               bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
    `name`             varchar(256) DEFAULT NULL COMMENT '优惠券名称',
    `shop_number`      bigint(20) DEFAULT NULL COMMENT '店铺编号',
    `source`           tinyint(1) DEFAULT NULL COMMENT '优惠券来源 0:店铺券 1:平台券',
    `target`           tinyint(1) DEFAULT NULL COMMENT '优惠对象 0:商品专属 1:全店通用',
    `goods`            varchar(64)  DEFAULT NULL COMMENT '优惠商品编码',
    `type`             tinyint(1) DEFAULT NULL COMMENT '优惠类型 0:立减券 1:满减券 2:折扣券',
    `valid_start_time` datetime     DEFAULT NULL COMMENT '有效期开始时间',
    `valid_end_time`   datetime     DEFAULT NULL COMMENT '有效期结束时间',
    `stock`            int(11) DEFAULT NULL COMMENT '库存',
    `receive_rule`     json         DEFAULT NULL COMMENT '领取规则',
    `consume_rule`     json         DEFAULT NULL COMMENT '消耗规则',
    `status`           tinyint(1) DEFAULT NULL COMMENT '优惠券状态 0:生效中 1:已结束',
    `create_time`      datetime     DEFAULT NULL COMMENT '创建时间',
    `update_time`      datetime     DEFAULT NULL COMMENT '修改时间',
    `del_flag`         tinyint(1) DEFAULT NULL COMMENT '删除标识 0:未删除 1:已删除',
    PRIMARY KEY (`id`),
    KEY                `idx_shop_number` (`shop_number`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1810967816300515330 DEFAULT CHARSET=utf8mb4 COMMENT='优惠券模板表';

2.引入 ShardingSphere Maven Jar 依赖

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core</artifactId>
    <version>5.3.2</version>
</dependency>

3.变更Application.yaml和创建ShardingSphere配置文件

#  Application.yaml 文件
spring:
  application:
    name: oneCoupon-merchant-admin
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/one_coupon_rebuild?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    username: root
    password: root

# 修改为 ShardingSphere 自定义驱动

spring:
  application:
    name: oneCoupon-merchant-admin
  datasource:
    driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
    url: jdbc:shardingsphere:classpath:shardingsphere-config.yaml

创建 shardingsphere-config.yaml 数据库分片配置文件 。

# 数据源集合
dataSources:
  # 自定义数据源名称,可以是 ds_0 也可以叫 datasource_0 都可以
  ds_0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/one_coupon_rebuild_0?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    username: root
    password: root
  ds_1:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/one_coupon_rebuild_1?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    username: root
    password: root

rules:
  - !SHARDING
    tables: # 需要分片的数据库表集合
      t_coupon_template: # 优惠券模板表
        # 真实存在数据库中的物理表
        actualDataNodes: ds_${0..1}.t_coupon_template_${0..8}
        databaseStrategy: # 分库策略
          standard: # 单分片键分库
            shardingColumn: shop_number # 分片键
            shardingAlgorithmName: coupon_template_database_mod # 库分片算法名称,对应 rules[0].shardingAlgorithms
        tableStrategy: # 分表策略
          standard: # 单分片键分表
            shardingColumn: shop_number # 分片键
            shardingAlgorithmName: coupon_template_table_mod # 表分片算法名称,对应 rules[0].shardingAlgorithms
    shardingAlgorithms: # 分片算法定义集合
      coupon_template_database_mod: # 优惠券分库算法定义
        type: HASH_MOD # 基于 Hash 方式分片
        props:
          sharding-count: 2 # 一共有 2 个库
      coupon_template_table_mod: # 优惠券分表算法定义
        type: HASH_MOD # 基于 Hash 方式分片
        props:
          sharding-count: 8 # 单库 8 张表

props:
  # 配置 ShardingSphere 默认打印 SQL 执行语句
  sql-show: true

📢在项目使用中,出现了数据分片不均匀的问题

image

因为我们的分片键shop_number经过数据库Hash后已经确定是奇数还是偶数了,所以哪怕Hash的数值(库和表数量)变了,但是仍然只能是奇数和偶数。

所以这里我们需要变更Hash算法,通过自定义的Hash算法扰动分片结果。

5.修改数据库表和分片设置

我们将 one_coupon_rebuild_1 数据库中的 t_coupon_template_0-7 表删除,创建更多的数据库表 。

分片配置有两个变更点,那就是数据库表的分片从每个数据库的0..8变更为所有数据库里的表0..16,以及从框架自带的HashMod分片算法修改为自定义分片算法。分片算法见下文所示。

rules:
  - !SHARDING
    tables: # 需要分片的数据库表集合
      t_coupon_template: # 优惠券模板表
        # 真实存在数据库中的物理表
        actualDataNodes: ds_${0..1}.t_coupon_template_${0..15}
        databaseStrategy: # 分库策略
          standard: # 单分片键分库
            shardingColumn: shop_number # 分片键
            shardingAlgorithmName: coupon_template_database_mod # 库分片算法名称,对应 rules[0].shardingAlgorithms
        tableStrategy: # 分表策略
          standard: # 单分片键分表
            shardingColumn: shop_number # 分片键
            shardingAlgorithmName: coupon_template_table_mod # 表分片算法名称,对应 rules[0].shardingAlgorithms
    shardingAlgorithms: # 分片算法定义集合
      coupon_template_database_mod: # 优惠券分库算法定义
        type: CLASS_BASED # 根据自定义库分片算法类进行分片
        props: # 分片相关属性
          # 自定义库分片算法Class
          algorithmClassName: com.nageoffer.onecoupon.merchant.admin.dao.sharding.DBHashModShardingAlgorithm
          sharding-count: 16 # 分片总数量
          strategy: standard # 分片类型,单字段分片
      coupon_template_table_mod: # 优惠券分表算法定义
        type: CLASS_BASED # 根据自定义库分片算法类进行分片
        props: # 分片相关属性
          # 自定义表分片算法Class
          algorithmClassName: com.nageoffer.onecoupon.merchant.admin.dao.sharding.TableHashModShardingAlgorithm
          strategy: standard # 分片类型,单字段分片

6.创建自定义分片算法
image

分库和分表算法类似,我们以分库算法解析:

@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {
    long id = shardingValue.getValue(); // 分片键值,也就是商家店铺编号
    int dbSize = availableTargetNames.size(); // 一共有多少个真实的数据库,咱们就两个 ds_0、ds_1
    int mod = (int) hashShardingValue(id) % shardingCount / (shardingCount / dbSize); // 取模
    int index = 0;
    // 通过刚才的数据库下标,获取到数据库逻辑名称 ds_0 或者 ds_1
    for (String targetName : availableTargetNames) {
        if (index == mod) {
            return targetName;
        }
        index++;
    }
    throw new IllegalArgumentException("No target found for value: " + id);
}

修改完分片算法之后 ,再次测试:
image

posted @ 2025-09-11 15:02  爱吃猪大肠  阅读(74)  评论(0)    收藏  举报