掘金 后端 ( ) • 2024-04-22 17:34

theme: orange

👩🏽‍💻个人主页:阿木木AEcru

🔥 系列专栏:《Docker容器化部署系列》 《Java每日面筋》

💹每一次技术突破,都是对自我能力的挑战和超越。

Docker部署MySql主从详细教程 - 掘金 (juejin.cn)

那天写了 部署mysql主从后,想了想,还是有必要出多一篇关于ShardingSphere-JDBC 读写分离、分库分表的文章,做就得做全套,那么今天就来实现一下。

一、前言

首先我们得知道读写分离以及分库分表的基本概念。

1.1 读写分离是什么?

读写分离其核心思想是将数据库的读操作(查询)和写操作(插入、更新、删除)分离到不同的数据库服务器上。这样做是为了提高数据库系统的处理能力和伸缩性,同时减轻单个数据库服务器的负载。

这就是为什么需要主库和从库的原因了,大部分的写操作都是在 主库进行操作的,而从库则承担了读操作,如果说需要更加的高可用的话,可以部署多出多从的架构。将读写操作分担导了不同的节点,更有利于服务的稳定性。不会说某一台的负载特别高。

1.2 分库分表是什么?

分库就是根据根据业务耦合性将一个库中的多张表拆分出来独立到另外一个库。例如在微服务结构中就需要分库,因为一个服务就需要一个库,而分库的粒度也是取决于你服务的粒度。

分表有两种分表的方式,垂直分表以及水平分表。

垂直分表:是将一个表中的一部分字段抽离出来做一个冗余的表,当一个表的字段非常多,且某些字段的访问频率较低或字段较大时,这时就非常适合使用垂直分表,将访问频率大的字段、或者较大(存储字符较多) 的字段抽离出来独立一张表。这样单张表的数据量就不会太大,从而提高查询效率

水平分表:当单表行数非常大,导致查询和更新操作变慢时,这时就适合使用水平分表, 将表的数据按照某种规则(如用户ID、时间等)分散到多个表中,每个表包含完整的表结构,但是数据是分散的。 这样单张表的行数就不会太大,一般单张表建议是在500w行左右。

总的来说,其实分表就是为了使得单张表的数据量不会过大而影响查询效率,当然最佳优化肯定还是需要优化查询SQL以及索引等方式来进行调优。

好了,了解了什么是读写分离以及分库分表后,就进入了今天的主题了,我们接着往下看。

二、ShardingSphere-JDBC 实现 读写分离

ShardingSphere-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。ShardingSphere官网

2.1 读写分离逻辑

读写分离则是根据 SQL 语义的分析,将读操作和写操作分别路由至主库与从库。需要注意的是,shardingSphere 不会帮你实现数据库的主从同步,这个就需要你自己去配置数据库了,文章开头也有mysql配置主从的教程链接,感兴趣的小伙伴可以去看看。

与ShardingSphere-proxy对比

这里简单讲讲ShardingSphere-proxy,它实际上是再请求到实际的数据库之前做了一层代理,请求先到了ShardingSphere-proxy 上,然后通过一系列的算法将sql分散在不同的数据库节点。这样的好处也就是不用每个服务都独立配置,只需要配置这一层代理就行了。坏处就是需要独立占用服务器的资源,处理请求的瓶颈也会转移到它这里。 这就有点像 Mycat了,感兴趣的小伙伴可以去了解一下。

2.2 实现读写分离

下面我将会通过一个demo来演示。主要代码我会在后面的贴出来。需要源码的可以私信博主。

这是demo的项目结构。

2.3 pom文件

<dependencies>
        <!--SpringBoot相关依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--Shardingjdbc-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>shardingsphere-jdbc-core</artifactId>
            <version>5.3.2</version>
        </dependency>
        <!--连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.9</version>
        </dependency>

        <!--mysql 驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>
        <!--mybatis plus extension,包含了mybatis plus core-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-extension</artifactId>
            <version>3.5.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- hutool工具类-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-crypto</artifactId>
            <version>5.8.10</version>
        </dependency>

        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>1.33</version>
        </dependency>
    </dependencies>

2.4 application.yml 配置文件

# 服务配置
server:
  # 应用程序运行的端口
  port: 9001
  # Servlet的上下文路径
  servlet:
    context-path: /sharding-jdbc

# 日志配置
logging:
  # 设置特定包(通常是应用程序包)的日志级别
  level:
    # 设置com.example.demo包下的类的日志级别为debug
    com.example.demo: debug
    # 设置com.example.demo.repository包下的类的日志级别为trace(更详细)
    com.example.demo.repository: trace

# Spring框架配置
spring:
  # 数据源配置
  datasource:
    # 驱动类名,这里使用的是ShardingSphere提供的驱动
    driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
    # 数据源URL,使用ShardingSphere的JDBC URL格式
    url: jdbc:shardingsphere:classpath:sharding-jdbc.yml
    # 这里指定了ShardingSphere的配置文件位置,该文件包含了分片规则等配置

  # JPA(Java Persistence API)配置
  jpa:
    # 是否显示执行的SQL语句
    show-sql: true
    # JPA属性配置
    properties:
      # Hibernate的配置属性
      hibernate:
        # 自动更新数据库表结构
        ddl.auto: update
        # 打印执行时间统计信息
        generate_statistics: true
      # 指定数据库方言,这里使用的是MySQL 5的InnoDB方言
      database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    # 是否在视图(View)中打开查询
    open-in-view: false

2.5 (重点)sharding-jdbc.yml 配置文件


# 数据源配置
dataSources:
  # 主数据库配置
  master1:
    # 数据源类名,这里使用的是阿里巴巴的Druid连接池
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    # JDBC驱动类名
    driverClassName: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,包括数据库地址、端口、数据库名以及连接参数
    url: jdbc:mysql://121.36.95.63:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true&allowMultiQueries=true&autoReconnect=true&serverTimezone=GMT%2B8
    # 数据库登录用户名
    username: root
    # 数据库登录密码
    password: root
  # 从数据库1配置
  slave1:
    # 数据源类名,与主数据库相同
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    # JDBC驱动类名,与主数据库相同
    driverClassName: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,与主数据库类似,但端口不同
    url: jdbc:mysql://121.36.95.63:3307/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true&allowMultiQueries=true&autoReconnect=true&serverTimezone=GMT%2B8
    # 数据库登录用户名,与主数据库相同
    username: root
    # 数据库登录密码,与主数据库相同
    password: root

# 规则配置
rules:
  # 声明使用读写分离规则
  - !READWRITE_SPLITTING
    # 读写分离的数据源配置
    dataSources:
      readwrite_ds:
        # 静态策略配置
        staticStrategy:
          # 写操作指向的主数据源名称
          writeDataSourceName: master1
          # 读操作指向的从数据源名称列表
          readDataSourceNames:
            - slave1
        # 负载均衡器名称
      loadBalancerName: round_robin
    # 负载均衡器配置
    loadBalancers:
      # 轮询负载均衡器
      round_robin:
        # 负载均衡器类型为轮询
        type: ROUND_ROBIN

# 属性配置
props:
  # 是否显示执行的SQL语句
  sql-show: true

2.6 Controller测试接口

其他代码我这边就不贴了,重点是上面配置文件,如果有 需要源码的小伙伴,可以私信我。

@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @GetMapping("/list")
    public R getList(){
        return R.ok(userService.list());
    }

    @PostMapping("/save")
    public R save(){
        User user = new User();
        user.setName("小明"+String.format("%06d",RandomUtils.nextInt(0,1000000)));
        user.setAddress("广州");
        user.setAge(RandomUtils.nextInt(18,100));
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        return R.ok(userService.save(user));
    }
}

2.7 请求测试结果

调用查询接口

调用写入接口

三、ShardingSphere-Jdbc 实现 数据分片(分库分表)

3.1 (重点)sharding-jdbc.yml 配置文件

# 数据源配置
dataSources:
  # 主数据库配置
  master1:
    # 数据源类名,这里使用的是阿里巴巴的Druid连接池
    dataSourceClassName: com.alibaba.druid.pool.DruidDataSource
    # JDBC驱动类名
    driverClassName: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,包括数据库地址、端口、数据库名以及连接参数
    url: jdbc:mysql://121.36.95.63:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true&allowMultiQueries=true&autoReconnect=true&serverTimezone=GMT%2B8
    # 数据库登录用户名
    username: root
    # 数据库登录密码
    password: root

# 分片规则配置
rules:
  # 读写分离配置
  - !READWRITE_SPLITTING
    # 定义读写分离的数据源
    dataSources:
      readwrite_ds:
        # 静态策略,定义写操作指向的数据源和读操作指向的数据源列表
        staticStrategy:
          writeDataSourceName: master1  # 写操作指向的主数据源名称
          readDataSourceNames:  # 读操作指向的从数据源名称列表
            - slave1
        # 负载均衡器名称
      loadBalancerName: round_robin
    # 定义负载均衡器的类型和策略
    loadBalancers:
      round_robin:
        type: ROUND_ROBIN  # 轮询负载均衡策略

  # 分表路由配置
  - !SHARDING
    # 定义需要分片的表及其分片策略
    tables:
      sys_log:
        # 定义实际的数据节点,包括数据源名称和表名
        actualDataNodes: master1.sys_log_$->{0..2}
        # 定义表的分片策略
        tableStrategy:
          standard:
            # 分片键和分片算法名称
            shardingColumn: id
            shardingAlgorithmName: t-log-inline
        # 定义键生成策略
        keyGenerateStrategy:
          column: id  # 指定键生成策略的列名
          keyGeneratorName: snowflake  # 指定使用的键生成器名称 这里使用的雪花算法


    # 定义分片算法
    shardingAlgorithms:
      t-log-inline:
        # 分片算法类型
        type: INLINE
        # 分片算法的属性配置
        props:
          algorithm-expression: sys_log_$->{id % 3}  # 分片算法表达式,根据id字段的值进行取模运算

    # 定义键生成器
    keyGenerators:
      snowflake:
        # 键生成器类型
        type: SNOWFLAKE

# 其他属性配置
props:
  sql-show: true  # 是否显示执行的SQL语句,便于调试

3.2 Controller 测试接口

3.3 写操作请求测试结果

这里通过 id 字段进行取模,有几张表就取模多少就行。这里使用的是最基础的分表策略,ShardingSphere-Jdbc 还支持 数据库分片,通过不同的规则计算 来进行 不同数据库的读写操作, 因为我这里 实现了读写分离,就不好做 数据库分片了, 不然数据库的数据会不一致。 感兴趣的小伙伴可以一起再评论区交流一下。 ShardingSphere-Jdbc 也还支持 各种分表策略,也可以自定义分表策略,常见的有,按照时间范围(年月日) , 按照 数据 哈希运算取模 、按照 业务字段 等。

3.4 读操作请求测试结果

这里由于没有用id字段查询,所以没有路由到对应的表来查,就会所有表都查询然后汇总结果。

当指定了id(分表路由字段)作为条件之后,就可以精准的路由到某张表查询,这样查询效率就会快很多。

四、结尾

感谢您的观看! 如果本文对您有帮助,麻烦用您发财的小手点个三连吧!您的支持就是作者前进的最大动力!再次感谢!