SpringCloud进阶--MySQL主从复制、分库分表、读写分离的实现

MySQL与分布式

主从复制

其思路和redis的主从复制思路相似,也是采用增量复制的方式。

MySQL在运行过程中,会记录二进制日志,所有的DML和DDL操作都会被记录到日志中,主库将操作记录复制给从库,从库也运行一次,就实现了主从复制。注意:它不会再一开始进行全量复制,所以最好在开始主从之前将数据库的内容保持一致。

主从复制模式下,主库挂掉,从库也能正常提供服务,并且还可以实现读写分离操作。

这里使用两台主机搭建一主一:

  1. 先在主库创建账号:

create user test identified with mysql_native_password by '123456'

  1. 开启外网访问:

sudo vim /etc/mysql/mysql.conf.d/mysqld.cof

  1. 修改配置文件:

注释掉 bind-address = 127.0.0.1

注释掉 mysqlx-bind-address = 127.0.0.1

然后重启MySQL服务。

  1. 配置主库,给刚才创建的用户分配一个主从复制的权限即可:
grant replication slave on *.* to test;
flush privileges;

这时主库就搭建完成了,然后开始进行从库配置

  1. 配置从库配置文件:
# 这里需要将server-id配置为其他的值(默认1),所有MySQL主从实例的id必须唯一!,不然会开启失败
server-id  = 2
  1. 进入数据库,输入:
change replication source to SOURCE_HOST='192.168.0.8',SOURCE_USER='test',SOURCE_PASSWORD='123456',SOURCE_LOG_FILE='BINLOG.000004',SOURCE_LOG_POS=591;

SOURCE_HOST -- 主库地址

SOURCE_LOG_FILE-- 二进制日志,就是主库的日志文件(在主库输入 show master status 可以查到)

SOURCE_LOG_POS -- 日志的偏移地址(在主库输入 show master status 可以查到)

  1. 执行完后输入start replica;

这时从库就启动了!

输入show replica status\G;

无标题

最关键的就是Replica_IO_Running和Replica_SQL_Running必须同时为yes才可以!

实际上从库会创建2个线程,一个线程复制与主库进行通信,获取二进制日志,暂时存放到一个中间表(Relay_Log)中,另一个线程将中间表保存的二进制日志信息进程执行,然后插入到从库中。

此时就配置完成了。在主库创建一个数据库,从库就会跟着创建一个数据库.

那要是主库挂了会怎么样?

这时IO线程处于重连状态,等待主库恢复运行.

主库恢复之后,它的日志表名变了,此时需要重新配置从库,不然从库使用的还是之前的错误日志表.

分库分表

大型互联网系统中,单台MySQL的存储容量无法满足需求,这时就需要扩容.

我们通过多台实例进行扩容,可以将数据分散,让多台主机共同保存数据.

那么如何进行拆分?

  • 垂直拆分:将数据库中的所有表,按照业务功能拆分到各个数据库中,而对于一张表,也可以通过外键之类的机制,将其拆分成多个表.

无1标题

  • 水平拆分:水平拆分针对的是数据,不是表.让多个具有相同表的数据库协同存放一部分数据,相当于把数据分散到各个节点上.

无标2题

实现这样的拆分操作,目前已经有一些解决方案了,比如mycat(数据库中间件,相当于挂了一层代理,再通过mycat进行分库分表操作数据库,只需要连接就能使用)或者Sharding JDBC(应用程序中字节对sql语句进行解析,然后转换成分库分表操作,需要自己编写一些逻辑代码),这里以Sharding JDBC为例!

Sharding JDBC

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

  • 适用于任何基于JDBC的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
  • 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
  • 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer,PostgreSQL以及任何遵循SQL92标准的数据库。

分库实现

具体实现水平拆分步骤如下:

  1. 新建一个spring boot项目.先导入依赖:
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
    <version>5.1.0</version>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>4.0.1</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
  1. 编写配置文件
spring:
  shardingsphere:
    datasource:
      # 有几个数据源,就写几个,名称+数字的形式
      names: ds0,ds1
      # 每个数据源的具体配置
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/yyds?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
        username: root
        password: 123456
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/yyds?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
        username: root
        password: 123456
  1. 编写实体类和Mapper接口
@AllArgsConstructor
@Data
public class User {
    int id;
    String name;
    String password;
}
@Mapper
public interface UserMapper {

    @Select("select * from user where id = #{id}")
    User getUserById(int id);

    @Insert("insert into user (id, name, password) values (#{id}, #{name}, #{password})")
    int addUser(User user);
}

这些都是常规操作,编写代码的关注点依然放在业务上.

现在需要编写配置文件,告诉Sharding JDBC要如何进行分片.(现在是两个数据库都有user表,我们的目标是将用户信息分别存放在这两个数据库中)

spring:
  shardingsphere:
    rules:
      sharding:
        tables:
          # 这里填写表名,程序中对这张表的所有操作 都会采用下面的路由方案
          user:
            # 实际的路由数据节点,我们要分两个库,每个库一个表,所以是两个数据节点,格式是 数据源名称.表名
            # 也可以使用表达式的方式来写,比如:ds$->{0..1}.user
            actual-data-nodes: ds0.user,ds1.user
            # 分库策略配置
            database-strategy:
              # 这里选择标准的分库策略,也可以配置复杂的分库策略,具体可以参考官方文档
              standard:
                # 参与分片运算的字段,通常是主键,下面的算法会根据这个字段的值来决定路由到哪个库
                sharding-column: id
                # 选择分片算法,下面我们会自定义一个分片算法,命名为my-alg
                sharding-algorithm-name: my-alg
      sharding-algorithms:
        # 自定义的分片算法,命名为my-alg
        my-alg:
          # 算法类型(更多类型可以查询官方文档),这里选择MOD算法,MOD算法会根据分片字段的值对分片数量根据2取模来决定路由到哪个库
          type: MOD
          props:
            shard-count: 2
    props:
      # 开启SQL日志输出,方便我们观察SQL路由的情况
      sql-show: true

这时就完成分库了,然后接可以进行业务开发了.

分表实现和查询

假如数据库中有test_01 和test_02两张表,表结构一样.我们希望能根据id取模运算的结果分别放到这两个不同的表中.首先需要明白两个概念:什么是逻辑表,什么是真实表

  • 逻辑表:相同结构的水平拆分数据库(表)的逻辑名称,是sql中表的逻辑标识,例如:订单数据根据主键拆分为10张表,分别是t_order_0到t_order_9,他们的逻辑表名就是t_order
  • 真实表:真实存在的表,就是上面的t_order_0到t_order_9

实现的时候,不用修改任何业务逻辑代码,只需要做相应的配置即可:

spring:
  shardingsphere:
    rules:
      sharding:
        tables:
          test:
            actual-data-nodes: ds0.test$->{0..1}
            # 配置分表策略
            table-strategy:
              standard:
                sharding-column: id
                sharding-algorithm-name: my-alg
        sharding-algorithms:
            my-alg:
              # INLINE方式,自定义表达式来决定路由到哪个库,表达式中可以使用分片字段的值来进行计算
                type: INLINE
                props:
                  algorithm-expression: test$->{id % 2}
                  # 查询也可以根据分片策略来进行,如果使用的是范围查询,依然会进行全量查询,这里先关闭
                  allow-range-query-with-inline-sharding: false

查询的时候,如果是根据id获取一行数据,实际上是去两张表里面查.

如果是范围查询,也就是返回结果是一个list集合的查询,需要配置allow-range-query-with-inline-sharding: true实际执行的sql是把两张表的查询结果union all起来,比如:

select * from test where id >10 -- 就是执行下面的sql
select * from test_0 where id >10 union all select * from test_1 where id >10 

分布式序列算法

在复杂分布式系统中,特别是微服务架构中,需要对大量数据和消息进行唯一标识.随着数据增多,分库分表成为常见方案,对数据分库分表后需要有一个唯一id来标识一条数据或消息(如订单号,事件编号等),此时需要一个能够生成全局唯一id的算法。这个id有以下要求:

  • 必须全局唯一
  • 尽可能保持有效,这样会提升索引的查询效率

满足以上需求的方案有:

  1. uuid:是一组32位数的16禁止数字随机构成,全局唯一 但无需。
  2. 雪花算法(Snowflake):生成一个64bit大小的整型id,需要使用Long类型。int类型装不下

无标3题

雪花算法虽然具备了唯一性和有序性,但是依然有缺点,第一就是时间问题,如果机器时间出现倒退,那么就会导致生成重复的id,并且节点容量只有1024个,如果是超大规模集群,也存在隐患。

这里让Sharding JDBC用雪花算法为我们生成主键:

  1. 先把表中的主键字段类型改为bigint
  2. 修改配置文件,添加对应配置:
spring:
  shardingsphere:
    rules:
      sharding:
        tables:
          user:
            actual-data-nodes: ds0.user,ds1.user
            # 分库策略配置
            database-strategy:
              # 这里选择标准的分库策略,也可以配置复杂的分库策略,具体可以参考官方文档
              standard:
                # 参与分片运算的字段,通常是主键,下面的算法会根据这个字段的值来决定路由到哪个库
                sharding-column: id
                # 选择分片算法,下面我们会自定义一个分片算法,命名为my-alg
                sharding-algorithm-name: my-alg
            key-generate-strategy:
              column: id
              key-generator-name: my-gen
        key-generators:
          # 自定义的主键生成算法,命名为my-gen
          my-gen:
            # 算法类型,这里选择SNOWFLAKE算法,SNOWFLAKE算法会生成全局唯一的ID,适合分布式环境
            type: SNOWFLAKE
            props:
            # SNOWFLAKE算法需要配置一个worker-id,通常是一个唯一的数字,可以根据实际情况来设置,确保在分布式环境中不同的实例有不同的worker-id
              worker-id: 666
        sharding-algorithms:
        # 自定义的分片算法,命名为my-alg
          my-alg:
          # 算法类型,这里选择MOD算法,MOD算法会根据分片字段的值对分片数量取模来决定路由到哪个库
            type: MOD
            props:
              shard-count: 2
    props:
      # 开启SQL日志输出,方便我们观察SQL路由的情况
      sql-show: true

此时就完成了主键生成策略设置

读写分离

之前实现的MySQL主从复制模式,我们可以将主库作为写操作,从库作为只读操作

无4标题

在实现了主从复制模式的基础上进行配置:

  1. 在从库的配置文件中,设置 read-only =1,这样从库就只能读数据了。

然后重启从库

  1. 配置Sharding JDBC
spring:
  shardingsphere:
    rules:
      # 配置读写分离
      readwrite-splitting:
        data-sources:
          #名称随便写
          user-db:
            type: static
            props:
              # 配置写库,只能写一个
              write-data-source-name: ds0
              # 配置读库,可以配置多个,逗号分隔
              read-data-source-names: ds1
              load-balancer-name: my-load
        load-balancers:
            # 自定义的负载均衡算法,命名为my-load
          my-load:
            type: round_robin

此时读写分离配置完成!

posted @ 2026-04-03 16:13  NE_STOP  阅读(2)  评论(0)    收藏  举报