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

 更新时间:2024年03月14日 09:27:56   作者:nana_Wang123  
在实际开发中,如果表的数据过大我们需要把一张表拆分成多张表,本文主要介绍了使用ShardingSphere实现MySQL分库分表,具有一定的参考价值,感兴趣的可以了解一下

ShardingSphere介绍

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

  • 一套开源的分布式数据库中间件解决方案
  • 有3个产品:JDBC、Proxy、Sidecar

分库分表

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

  • 一般来说, MySQL推荐的单表数据量在500w ~ 800w, 超过800w则建议分表.
  • 或是在系统接口响应时间明显变慢, 并且通过代码优化, 改写sql等形式无法获得明显提升, 且明确了性能瓶颈在数据库时, 建议分表
  • 分库则是在分表后单库性能到达瓶颈后进行, 如果个人或项目有钱任性的除外.分库主要解决的是并发量大的问题。

垂直拆分

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

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

优点:

  • 拆分后业务清晰,拆分规则明确。
  • 系统之间整合或扩展容易。
  • 数据维护简单。

缺点:

  • 部分业务表无法 join,只能通过接口方式解决,提高了系统复杂度。
  • 受每种业务不同的限制存在单库性能瓶颈,不易数据扩展跟性能提高。
  • 事务处理复杂。

水平拆分

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

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

优点:

  • 不存在单库大数据,高并发的性能瓶颈。
  • 对应用透明,应用端改造较少。
  • 按照合理拆分规则拆分,join 操作基本避免跨库。
  • 提高了系统的稳定性跟负载能力。

缺点:

  • 拆分规则难以抽象。
  • 分片事务一致性难以解决。
  • 数据多次扩展难度跟维护量极大。
  • 跨库 join 性能较差。

分片后的常见问题

数据倾斜
这个几乎是无法避免的, 即使是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分库分表内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解SpringBoot+SpringSecurity+jwt整合及初体验

    详解SpringBoot+SpringSecurity+jwt整合及初体验

    这篇文章主要介绍了详解SpringBoot+SpringSecurity+jwt整合及初体验,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-06-06
  • Java数据类型分类与基本数据类型转换

    Java数据类型分类与基本数据类型转换

    这篇文章主要介绍了Java数据类型分类与基本数据类型转换,Java的数据类型主要分为两类,基本数据类型、引用数据类型,下文详细介绍,感兴趣的朋友可以参考一下
    2022-07-07
  • java实现弹球小游戏

    java实现弹球小游戏

    这篇文章主要为大家详细介绍了java实现弹球小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • Springboot实现过滤器的两种方式

    Springboot实现过滤器的两种方式

    今天通过本文给大家分享Springboot实现过滤器的两种方式,第一种是spring容器注册filter,第二种方式是通过@WebFilter 注解来配置,本文通过实例代码给大家介绍的非常详细,需要的朋友参考下吧
    2023-10-10
  • java导出到excel常用的几种方式总结

    java导出到excel常用的几种方式总结

    导出excel是咱Java开发的必备技能啦,之前项目有这个功能,现在将其独立出来,分享一下,下面这篇文章主要给大家介绍了关于java导出到excel常用的几种方式,需要的朋友可以参考下
    2023-05-05
  • Spring中Bean命名的方式总结

    Spring中Bean命名的方式总结

    在 Spring 框架中,每个 bean 必须至少有一个唯一的名称,这篇文章主要为大家详细介绍了Spring中Bean命名的各种方式,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-12-12
  • java封装实例用法讲解

    java封装实例用法讲解

    在本篇文章里小编给大家整理了一篇关于java封装实例用法及相关知识点,有兴趣的朋友们可以参考下。
    2021-01-01
  • Java方法签名的获取实例代码

    Java方法签名的获取实例代码

    这篇文章主要介绍了Java方法签名的获取实例代码,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-02-02
  • Springboot集成任务调度实现过程

    Springboot集成任务调度实现过程

    这篇文章主要介绍了Springboot集成任务调度实现过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • SpringBoot3和mybatis-plus整合出现的问题解决办法

    SpringBoot3和mybatis-plus整合出现的问题解决办法

    SpringBoot和MybatisPlus的整合可以让我们更加方便地进行数据库操作,这篇文章主要给大家介绍了关于SpringBoot3和mybatisplus整合出现的一些问题的相关资料,需要的朋友可以参考下
    2024-01-01

最新评论