java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > ShardingSphere MySQL分库分表

ShardingSphere结合MySQL实现分库分表的项目实践

作者:nana_Wang123

在实际开发中,如果表的数据过大我们需要把一张表拆分成多张表,本文主要介绍了使用ShardingSphere实现MySQL分库分表,具有一定的参考价值,感兴趣的可以了解一下

ShardingSphere介绍

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

分库分表

当我们使用读写分离、索引、缓存后,数据库的压力还是很大的时候,这就需要使用到数据库拆分了。

垂直拆分

分表
表中的字段较多,一般将不常用的、 数据较大、长度较长的拆分到“扩展表“。一般情况加表的字段可能有几百列,此时是按照字段进行数竖直切。注意垂直分是列多的情况。

分库
一个数据库的表太多。此时就会按照一定业务逻辑进行垂直切,比如用户相关的表放在一个数据库里,订单相关的表放在一个数据库里。注意此时不同的数据库应该存放在不同的服务器上,此时磁盘空间、内存、TPS等等都会得到解决。

优点:

缺点:

水平拆分

分表
单表的数据量太大。按照某种规则(RANGE,HASH取模等),切分到多张表里面去。 但是这些表还是在同一个库中,所以库级别的数据库操作还是有IO瓶颈。这种情况是不建议使用的,因为数据量是逐渐增加的,当数据量增加到一定的程度还需要再进行切分。比较麻烦。

分库
水平分库理论上切分起来是比较麻烦的,它是指将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。 水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈。

优点:

缺点:

分片后的常见问题

数据倾斜
这个几乎是无法避免的, 即使是id取模, 也会因为数据的删除导致每张分表的数据不一样, 或者id是UUID, 取模也会导致数据发生倾斜. 但是一般来说倾斜只要不是太离谱, 都在我们的接受范围以内.

id生成策略
如果分片之前你的id是递增的, 那么分片后你就无法保证id的全局唯一性, 这时比较常见的业内方案就是UUID或者SnowFlake.
当然如果想要排序和分页, 就需要有个id生成器去统一集中生成连续的id(参考下文).

全路由
这个是最糟糕的情况, 这种情况会让我们的查询比分片之前还要慢, 可以在自定义的分片算法中校验这种情况直接抛出异常, 然后coder们根据日志中的报错来统计这部分sql加以改写.

jpa级联
如果jpa级联中包含分表, 则需要拆除这种级联关系, 以免导致上述全路由情况发生.

排序&分页
如果只是单独分页, Sharding Sphere会剔除数据不写入内存, 实际上不会导致内存的大量占用, 但如果加上排序, 那情况就不容乐观了, 官方建议通过可以保证连续性的id去加以限制.

四种分片算法&五种分片策略

4种分片算法

精确分片算法
对应PreciseShardingAlgorithm,用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用。

范围分片算法
对应RangeShardingAlgorithm,用于处理使用单一键作为分片键的BETWEEN AND、>、<、>=、<=进行分片的场景。需要配合StandardShardingStrategy使用。

复合分片算法
对应ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合ComplexShardingStrategy使用。

Hint分片算法
对应HintShardingAlgorithm,用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。

5种分片策略

标准分片策略
对应StandardShardingStrategy。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND, >, <, >=, <=分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。

复合分片策略
对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。

行表达式分片策略
对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: t_user_$->{u_id % 8} 表示t_user表根据u_id模8,而分成8张表,表名称为t_user_0t_user_7

Hint分片策略
对应HintShardingStrategy。通过Hint指定分片值而非从SQL中提取分片值的方式进行分片的策略。

不分片策略
对应NoneShardingStrategy。不分片的策略。

ShardingSphere-JDBC

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

需要注意的是,分库分表并不是由 ShardingSphere-JDBC 来做,它是用来负责操作已经分完之后的 CRUD 操作。

水平分表实操(单分片键,分片类型:HASH_MODE)

环境使用:SpringBoot 2.7.12 + MybatisPlus + ShardingSphere-jdbc 5.2.0 + Druid连接池

本示例为单库,库内有6个分表,并且按照order_id的hash值进行取模计算得到实际表

添加Maven依赖

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
</dependency>

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
    <version>5.1.0</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
</dependency>

按照水平分表创建数据库

CREATE TABLE `t_order_5`
(
    `order_id`    bigint NOT NULL AUTO_INCREMENT,
    `price`       double(12,2),
    `user_id`     int    NOT NULL,
    `address_id`  bigint NOT NULL,
    `city`        varchar(32) NULL DEFAULT NULL,
    `status`      tinyint NULL DEFAULT NULL,
    `interval_time` datetime NULL DEFAULT NULL,
    `creator`     varchar(32) NULL DEFAULT NULL,
    `create_time` datetime NULL DEFAULT NULL,
    `updater`     varchar(32) NULL DEFAULT NULL,
    `update_time` datetime NULL DEFAULT NULL,
    PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic

配置Sharding-jdbc分片策略

application.yml内容:

spring:
  main:
    banner-mode: off

  shardingsphere:
    # 配置数据源,给数据源起别名ds
    datasource:
      ds:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
        username: root
        password: 1234
      names: ds
    rules:
      sharding:
        binding-tables:
          - t_order,t_order_item
        broadcast-tables: t_address
        # 分片算法
        sharding-algorithms:
          t-order-algorithm:
            type: HASH_MOD
            props:
              sharding-count: '6'
          t-order-item-inline:
            type: INLINE
            props:
              algorithm-expression: t_order_item_$->{order_id % 2}
        tables:
        # 分表
          t_order:
            actual-data-nodes: ds.t_order_$->{0..5}   # 真实表名
            table-strategy:
              standard:
                sharding-algorithm-name: t-order-algorithm
                sharding-column: order_id   # 分片键
          t_order_item:
            actual-data-nodes: ds.t_order_item_$->{0..1}
            table-strategy:
              standard:
                sharding-algorithm-name: t-order-item-inline
                sharding-column: order_id
    props:
      sql-show: true



#mybatis-plus:
#  global-config:
#    banner: false
#    db-config:
#      id-type: assign_id  #使用雪花算法开启数据入库时ID自增
#      logic-delete-field: deleted
#      logic-delete-value: 1
#      logic-not-delete-value: 0
#  #开启mp的日志(控制台输出)
#  configuration:
#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

mybatis:
  mapper-locations: classpath*:/mappers/*-mapper.xml
  type-aliases-package: com.panliu.domain
  configuration:
    default-fetch-size: 20
    default-statement-timeout: 30
    map-underscore-to-camel-case: true
    use-generated-keys: true

测试代码运行

@Slf4j
@SpringBootTest
public class OrderMapperTests {
    private final static String[] CITIES = {"shanghai", "beijing"};
    /**
     * -120000, 0, 18, 20000, 40000, 50000, 60000, 64000, 80000, 99000
     */
    private final static long[] PRICES = {-120000, 0, 18, 20000, 40000, 50000, 60000, 64000, 80000, 99000};
    @Autowired
    private OrderMapper orderMapper;

    @Test
    //@Disabled
    void save() {
        ThreadLocalRandom random = ThreadLocalRandom.current();

        Date[] dates = create();
        IntStream.range(0, 20).forEach(i -> {
            Order order = new Order();
            order.setOrderId(System.nanoTime() + i);
            order.setPrice(PRICES[i % PRICES.length]);
            order.setAddressId(i);
            order.setCity(CITIES[i % 2]);
            order.setUserId(Math.abs(random.nextInt()));
            order.setCreator("user.0" + i);
            order.setIntervalTime(dates[i % 5]);
            order.setUpdater(order.getCreator());
            log.info("====>{}", order);
            orderMapper.save(order);
        });
    }

    @Test
   // @Disabled
    void findAll() {
        List<Order> list = orderMapper.findAllAtPrice(60000);
        log.info("===>{}", list);
    }

    private Date[] create() {
        Date[] dates = new Date[6];

        try {
            Date date = DateUtils.parseDate("2023-08-14 00:00:00", Locale.CHINA, "yyyy-MM-dd HH:mm:ss");
            IntStream.range(0, 6).forEach(i -> dates[i] = DateUtils.addDays(date, i));
        } catch (ParseException e) {
            log.error("date parse fail: ", e);
        }
        return dates;
    }
}

运行结果

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

复合分片(根据城市和order_id分片)

application.yml配置

rules:
  sharding:
    sharding-algorithms:
      t-order-algorithm:
        type: COMPLEX_INLINE
        props:
          algorithm-expression: t_order_$->{city}_$->{order_id % 2}
          sharding-columns: city,order_id
    tables:
      t_order:
        actual-data-nodes: ds.t_order_$->{['shanghai','beijing']}_$->{0..1}
        table-strategy:
          complex:
            sharding-algorithm-name: t-order-algorithm
            sharding-columns: city,order_id

分片结果

在这里插入图片描述

在这里插入图片描述

到此这篇关于使用ShardingSphere实现MySQL分库分表的文章就介绍到这了,更多相关ShardingSphere MySQL分库分表内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文