MySQL 的主从复制数据同步

一、什么是 MySQL 的主从复制

MySQL 的主从复制(Master-Slave Replication)是一种将数据从一个主数据库服务器(主库)复制到一个或多个从数据库服务器(从库)的技术。主库负责所有的数据写操作,从库则通过读取主库的二进制日志来同步主库的数据变化。主从复制主要用于实现数据的备份、负载分担和高可用性。

二、具体流程

1. 主库记录写操作

当主库执行写操作(如 INSERTUPDATEDELETE)时,这些操作会被记录到二进制日志(binlog)中。二进制日志保存了所有数据更改的历史记录,这些记录将成为从库同步数据的来源。

  • 步骤:主库将所有写操作(数据变更)记录到二进制日志中。


2. 从库连接主库并读取二进制日志

从库通过一个称为IO 线程的进程与主库建立连接,开始读取主库的二进制日志。主库会将日志内容发送给从库的 IO 线程。

  • 步骤:从库的 IO 线程连接主库,持续接收主库二进制日志的最新内容。


3. 从库将二进制日志写入中继日志

从库的 IO 线程将接收到的主库二进制日志复制到从库的**中继日志(relay log)**中。中继日志在从库本地保存了一份主库数据变更的副本,供从库执行使用。

  • 步骤:从库的 IO 线程将接收的日志写入中继日志,为数据变更做好准备。


4. 从库应用中继日志,执行数据同步

从库的另一个线程(称为SQL 线程)负责读取中继日志中的内容,并逐条在从库上执行,以实现数据的同步。每当主库有新的数据变更,从库都会从中继日志中获取并执行这些变更,从而保持与主库的数据一致。

  • 步骤:从库的 SQL 线程从中继日志中读取并执行记录的操作,逐步更新从库数据。


5. 持续复制和同步

整个主从复制过程是一个持续的流程,只要主库有新的数据变更,从库就会自动获取并执行对应的更改,从而保持与主库数据的一致性。主从复制会持续保持同步,以确保从库能够实时或接近实时地反映主库的最新数据。

  • 步骤:主库的所有写操作都会记录到日志中,实时同步到从库;从库持续读取并执行日志内容,更新自身数据。

三、作用和好处

  • 读写分离,提高性能

    • 减轻主库压力:将大量的读操作分散到从库,主库主要负责写操作,大大降低了主库的负载压力。
    • 提升并发处理能力:增加从库的数量可以提升系统的读并发能力,处理更多用户的并发请求,提升系统整体性能。
  • 高可用性和容错性

    • 数据冗余与容灾:主从复制提供了数据的实时备份,从库可以在主库发生故障时快速接管服务,提高系统的可用性。
    • 故障切换:当主库出现故障时,可以临时将从库升级为主库,保障服务持续运行。
  • 负载均衡

    • 分担查询压力:多个从库可以共同承担读取请求,通过负载均衡算法(如轮询、随机等)分配请求,提升系统的稳定性。
    • 避免单点瓶颈:读操作分散到不同的从库上执行,避免了单一数据库因访问量过大而成为系统瓶颈。
  • 扩展性

    • 水平扩展:根据业务增长,灵活增加从库数量,满足性能需求,而无需对主库进行大幅度改造。
    • 弹性扩展:可以根据实际流量,增加或减少从库,做到灵活应对负载变化。
  • 数据备份和安全性

    • 数据保护:从库可以用于数据备份和容灾,防止主库故障导致的数据丢失。
    • 快速恢复:在数据意外丢失或损坏时,可以通过从库恢复主库的数据。

四、在实际应用中的场景与示例

1. 电商平台

在电商平台中,用户的浏览和查询操作会产生大量的读请求,而订单创建、支付等操作会产生写请求。通过主从复制的读写分离:

  • 查询库存、商品详情等高频率的读操作可以由从库承担,减轻主库压力。
  • 用户下单、支付等写操作由主库负责,确保数据的完整性和一致性。

2. 社交媒体或内容网站

在社交媒体应用中,大量的内容浏览和搜索会产生大量的读操作,而发布、点赞等则是写操作。通过主从复制,平台可以:

  • 使用从库来承担浏览、查询等操作,确保高并发下的快速响应。
  • 将写操作指向主库,确保用户发布或点赞的实时更新。

五、在 Spring Boot 项目中集成 MySQL 主从复制

在 Spring Boot 项目中集成 MySQL 的主从复制数据同步,指的是将 Spring Boot 应用程序连接到配置有主从复制的 MySQL 数据库系统上,完成主从数据库的配置管理,实现自动的读写分离。具体来说,就是通过多数据源配置,让 Spring Boot 自动识别是写请求还是读请求,并将写请求发送到主库,读请求发送到从库。

集成的关键要素:

  • 多数据源配置:在 Spring Boot 中配置主数据库和从数据库的连接。
  • 动态数据源路由:在应用层面实现数据源的动态选择,读操作使用从库,写操作使用主库。
  • 读写分离注解或切面:利用自定义注解或 AOP 切面控制数据源的切换。

1.配置 MySQL 的主从复制环境

配置 MySQL 的主从复制环境是实现 MySQL 主从复制数据同步的第一步。主要步骤包括设置主库(Master)和从库(Slave),并验证主从复制的成功。

1.1 设置主库(Master)

配置主库是主从复制的第一步。主库负责记录数据变更并将其传递给从库。

1.1.1 修改主库的配置文件

在主库的 MySQL 配置文件中(通常位于 /etc/my.cnf/etc/mysql/my.cnf),需要启用二进制日志并为主库设置一个唯一的 server-id。在 [mysqld] 部分添加如下配置:

[mysqld]
server-id=1                 # 主库的唯一 ID
log-bin=mysql-bin           # 启用二进制日志,主从复制依赖于此
binlog-do-db=your_database  # 需要同步的数据库名称,多个数据库可添加多行
  • server-id:用于标识每个 MySQL 实例的唯一标识符,主库的 server-id 一般设置为 1。
  • log-bin:启用二进制日志,这是主从复制的基础。
  • binlog-do-db:指定需要同步的数据库(多个数据库可以多行设置)。

注意:如果需要同步多个数据库,可以多次添加 binlog-do-db 行,例如:

binlog-do-db=database1
binlog-do-db=database2

1.1.2 重启 MySQL 服务

修改配置文件后,需要重启 MySQL 以使配置生效:

# Linux 系统
sudo systemctl restart mysqld

# Windows 系统
net stop mysql
net start mysql

1.1.3 创建用于复制的用户

在主库中创建一个用于复制的用户,并授予 REPLICATION SLAVE 权限。这个用户用于从库连接主库并进行数据同步。

在主库的 MySQL 命令行中执行以下命令:

CREATE USER 'replica_user'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'replica_user'@'%';
FLUSH PRIVILEGES;
  • replica_user:用于复制的用户名,可以自定义。
  • password:复制用户的密码,注意使用强密码。
  • %:允许所有远程 IP 访问。如果只允许特定从库连接,可以用从库的 IP 地址代替 %

1.1.4 获取主库的二进制日志信息

为了让从库知道从何处开始复制数据,主库需要提供当前的二进制日志位置。可以通过以下命令查看:

SHOW MASTER STATUS;

命令执行后,会输出如下内容:

+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000001 |      154 | your_database|                  |
+------------------+----------+--------------+------------------+
  • File:当前的二进制日志文件名。
  • Position:二进制日志的偏移量,从该位置开始读取数据。

建议:在生产环境中,执行 SHOW MASTER STATUS; 之前,可以先执行 FLUSH TABLES WITH READ LOCK; 来锁定表,防止数据写入导致的数据不一致。获取信息后再执行 UNLOCK TABLES; 解锁。


1.2 设置从库(Slave)

配置从库是主从复制的第二步。配置从库连接到主库并开始同步数据。

1.2.1 修改从库的配置文件

在从库的 MySQL 配置文件中(通常是 /etc/my.cnf/etc/mysql/my.cnf),设置一个唯一的 server-id 并配置中继日志和只读模式。 在 [mysqld] 部分添加以下配置:

[mysqld]
server-id=2                 # 从库的唯一 ID,不能与主库或其他从库相同
relay-log=relay-log         # 设置中继日志文件名前缀
read-only=1                 # 设置只读模式,防止误操作
  • server-id:从库的唯一标识符,每个从库的 server-id 也需要是唯一的,通常从 2 开始。
  • relay-log:指定中继日志的前缀,用于存储从主库同步的数据。
  • read-only:开启只读模式,防止意外写入。此模式下,拥有 SUPER 权限的用户仍可以写入数据。

1.2.2 重启 MySQL 服务

修改配置文件后,重启从库的 MySQL 服务以应用配置:

# Linux 系统
sudo systemctl restart mysqld

# Windows 系统
net stop mysql
net start mysql

1.2.3 配置从库连接到主库

在从库的 MySQL 中,配置主库信息以便开始复制。需要用到在主库上记录的 FilePosition 值。

CHANGE MASTER TO
    MASTER_HOST='主库的 IP 地址',
    MASTER_USER='replica_user',
    MASTER_PASSWORD='password',
    MASTER_LOG_FILE='mysql-bin.000001',  -- 主库的 File 值
    MASTER_LOG_POS=154;                  -- 主库的 Position 值
  • MASTER_HOST:主库的 IP 地址。
  • MASTER_USER:在主库上创建的用于复制的用户(如 replica_user)。
  • MASTER_PASSWORD:复制用户的密码。
  • MASTER_LOG_FILE:主库的二进制日志文件名。
  • MASTER_LOG_POS:二进制日志的位置偏移量。

1.2.4 启动复制进程

配置完成后,启动从库的复制进程以开始从主库复制数据:

START SLAVE;

1.2.5 查看从库的复制状态

运行以下命令检查从库的状态,确认从库已成功连接到主库并开始复制数据:

SHOW SLAVE STATUS\G
  • Slave_IO_Running:应为 Yes,表示从库的 IO 线程正在读取主库的日志。
  • Slave_SQL_Running:应为 Yes,表示从库的 SQL 线程正在执行主库传递的日志。
  • Last_IO_ErrorLast_SQL_Error:如果存在错误信息,会在此处显示。

如果 Slave_IO_RunningSlave_SQL_RunningNo,请查看 Last_IO_ErrorLast_SQL_Error 中的错误消息,并根据错误提示排查问题。


1.3 验证主从复制是否成功

在配置完成后,验证主从复制的成功性。确保主库的写操作能够被从库正确同步。

1.3.1 在主库上创建测试数据

在主库上选择用于复制的数据库,并创建一个测试表插入数据:

USE your_database;
CREATE TABLE test_table (
    id INT PRIMARY KEY,
    name VARCHAR(50)
);

INSERT INTO test_table (id, name) VALUES (1, 'Test Data');

1.3.2 在从库上检查数据同步情况

在从库上选择相同的数据库,查询 test_table 表,确认是否同步了主库的数据:

USE your_database;
SELECT * FROM test_table;
  • 如果可以看到 Test Data,则表示主从复制配置成功并且工作正常。

1.3.3 验证实时同步效果

在主库上继续插入或更新数据,再次在从库上查询,验证数据是否能够及时同步。主从复制一般情况下会立即同步,延迟较小。


1.4 故障排查

在配置主从复制的过程中,可能会遇到以下常见问题:

1 从库无法连接到主库

  • 检查网络连接:确保主库的防火墙允许从库的 IP 地址访问 MySQL 的 3306 端口。

2 权限问题

  • 用户权限:确保在主库上创建的用户拥有 REPLICATION SLAVE 权限。

3 主从版本兼容性问题

  • 版本兼容性:确保主库和从库的 MySQL 版本相互兼容,推荐使用相同的版本。

4 日志位置不正确

  • 文件和位置错误:如果配置的 FilePosition 不正确,可以重新设置主从复制位置。

2.在 Spring Boot 项目中配置多数据源

配置多数据源是实现主从复制的重要步骤。通过多数据源配置,Spring Boot 应用可以自动区分并选择主库或从库,从而实现读写分离。这一步的具体操作包括添加依赖、配置数据源信息,以及配置数据源路由以实现自动选择主库或从库。以下是详细的分步讲解。


2.1 添加必要的依赖

在使用 Spring Data JPA 和 MySQL 多数据源时,需要添加以下依赖项:

  • MySQL 驱动:支持连接 MySQL 数据库。
  • Spring Data JPA:提供 JPA 支持,简化数据库操作。
  • HikariCP 连接池:Spring Boot 默认的连接池,适合高并发环境且支持多数据源配置。

在项目的 pom.xml 中添加以下依赖:

<dependencies>
    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <!-- Spring Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- HikariCP 连接池(Spring Boot 默认连接池) -->
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
    </dependency>
</dependencies>

2.2 在配置文件中定义主库和从库的数据源信息

接下来,需要在 application.properties 中定义主库和从库的连接信息。主库通常用于写操作,从库用于读操作。

application.properties 文件内容

# 主库配置
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.master.url=jdbc:mysql://主库IP地址:3306/your_database?useSSL=false&characterEncoding=utf8
spring.datasource.master.username=主库用户名
spring.datasource.master.password=主库密码

# 从库配置
spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.slave.url=jdbc:mysql://从库IP地址:3306/your_database?useSSL=false&characterEncoding=utf8
spring.datasource.slave.username=从库用户名
spring.datasource.slave.password=从库密码

说明

  • spring.datasource.master:主库连接信息,用于写操作。
  • spring.datasource.slave:从库连接信息,用于读操作。
  • driver-class-name:MySQL 驱动类名。
  • url:数据库连接 URL,其中包含数据库的 IP 地址和数据库名称。
  • usernamepassword:数据库的用户名和密码。

2.3 创建数据源配置类,配置主从数据源

在项目中添加一个配置类,用于定义主库和从库数据源,并通过一个动态数据源实现自动选择主库或从库。

2.3.1 配置主库和从库的数据源

首先,我们需要在配置类中定义两个数据源,即主库和从库。主库主要用于写操作,从库用于读操作。

package com.example.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import org.springframework.boot.jdbc.DataSourceBuilder;

@Configuration
public class DataSourceConfig {

    // 定义主库数据源
    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    // 定义从库数据源
    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }
}

说明

  • @ConfigurationProperties:这个注解将 application.propertiesspring.datasource.masterspring.datasource.slave 配置的信息绑定到 masterDataSourceslaveDataSource 上。这样,Spring Boot 会自动读取配置文件中的主从库信息,并将它们分别注入到两个数据源对象中。

  • DataSourceBuilder:这是 Spring 提供的一个工具类,通过它可以根据配置文件构建 DataSource 对象。DataSource 是与数据库连接的核心组件,主要用于管理数据库连接、连接池等信息。

2.3.2 动态数据源路由

为了实现主从分离,我们需要根据请求自动选择主库或从库的数据源。AbstractRoutingDataSource 是 Spring 提供的一个抽象类,可以根据用户定义的路由规则动态选择数据源。

创建 DynamicDataSource:该类继承自 AbstractRoutingDataSource,通过设置键值映射来实现数据源选择。

package com.example.config;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    // 设置当前线程的数据源(主库或从库)
    public static void setDataSource(String dataSourceKey) {
        contextHolder.set(dataSourceKey);
    }

    // 清除当前线程的数据源
    public static void clearDataSource() {
        contextHolder.remove();
    }

    // 确定当前数据源(由 AbstractRoutingDataSource 调用)
    @Override
    protected Object determineCurrentLookupKey() {
        return contextHolder.get();
    }
}

代码详解

  • contextHolder:使用 ThreadLocal 存储当前线程的数据源标识(masterslave)。ThreadLocal 确保每个线程拥有独立的变量副本,不会干扰其他线程。

  • setDataSource:用于设置当前线程的数据源,通过传入 masterslave 来指定主库或从库。

  • clearDataSource:用于清除当前线程的数据源,避免数据源信息在线程之间混淆。

  • determineCurrentLookupKey:这是 AbstractRoutingDataSource 提供的方法,它会在每次数据库操作时调用。根据 contextHolder 中的数据源标识,选择主库或从库。

2.3.3 将主从数据源注入到动态数据源

我们需要将主库和从库的数据源注入到动态数据源中,并根据不同的业务需求自动选择。

@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource(
        @Qualifier("masterDataSource") DataSource masterDataSource,
        @Qualifier("slaveDataSource") DataSource slaveDataSource) {

    Map<Object, Object> targetDataSources = new HashMap<>();
    targetDataSources.put("master", masterDataSource);
    targetDataSources.put("slave", slaveDataSource);

    DynamicDataSource dynamicDataSource = new DynamicDataSource();
    dynamicDataSource.setDefaultTargetDataSource(masterDataSource); // 设置默认的数据源
    dynamicDataSource.setTargetDataSources(targetDataSources);      // 将主库和从库的数据源加入路由

    return dynamicDataSource;
}

代码详解

  • @Qualifier:指定 masterDataSourceslaveDataSource,通过依赖注入的方式将主库和从库的数据源传入 dynamicDataSource

  • targetDataSources:将 masterslave 作为键,分别映射到 masterDataSourceslaveDataSourceDynamicDataSource 会通过 determineCurrentLookupKey 方法自动选择对应的数据源。

  • setDefaultTargetDataSource:设置默认的数据源。在没有指定数据源的情况下,系统会使用默认数据源(一般为主库)。

2.3.4 配置 EntityManagerFactory 使用动态数据源

对于使用 JPA 的项目,EntityManagerFactory 是 JPA 的核心组件之一。它负责管理实体管理器,处理 JPA 的持久化操作。这里需要将 EntityManagerFactory 配置为使用动态数据源,以实现主从分离。

@Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
        @Qualifier("dynamicDataSource") DataSource dynamicDataSource) {
    LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
    em.setDataSource(dynamicDataSource); // 使用动态数据源
    em.setPackagesToScan("com.example.entity"); // 实体类包名
    em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); // 设置 JPA 供应商为 Hibernate
    return em;
}

代码详解

  • setDataSource:设置 JPA 的数据源为 dynamicDataSource,这样 JPA 会根据动态数据源的路由选择合适的数据源。

  • setPackagesToScan:指定 JPA 实体类所在的包路径,JPA 会扫描该包中的实体类,进行数据库操作的映射。

  • HibernateJpaVendorAdapter:设置 JPA 的供应商适配器。这里我们使用 Hibernate 作为 JPA 的实现,它提供了 Hibernate 特有的优化和配置支持。

通过配置 EntityManagerFactory 使用 dynamicDataSource,我们可以让 JPA 在操作数据库时自动根据业务需要切换到主库或从库,从而实现主从分离和读写分离。


2.3.5 配置事务管理器

Spring Data JPA 默认需要一个事务管理器来管理事务。我们需要为动态数据源配置一个 JpaTransactionManager,以确保事务操作可以正确地应用于当前选择的数据源(主库或从库)。

@Bean(name = "transactionManager")
public JpaTransactionManager transactionManager(
        @Qualifier("entityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
    return new JpaTransactionManager(entityManagerFactory.getObject());
}

代码详解

  • JpaTransactionManager:Spring 提供的 JPA 事务管理器,它会自动处理 EntityManager 的创建和关闭,并确保在事务中对数据库操作的 ACID 特性(原子性、一致性、隔离性、持久性)。

  • entityManagerFactory.getObject():将 entityManagerFactory(配置了动态数据源的实体管理器工厂)传递给 JpaTransactionManager。这样,事务管理器会根据动态数据源路由来管理事务,确保事务一致性。

3.实现数据源的动态切换

为了实现数据库的主从切换,使得 Spring Boot 项目可以根据操作类型自动选择主库或从库,我们需要实现数据源的动态切换。实现动态切换的关键步骤包括:定义自定义注解、创建 AOP 切面,在方法执行时动态地决定使用哪个数据源。


3.1 创建 @DataSource 注解(用于标识使用哪个数据源)

首先,我们创建一个自定义注解 @DataSource,用于标识在特定方法或类上指定的数据源类型(如主库 master 或从库 slave)。有了这个注解之后,我们可以在代码中灵活地指定哪些操作使用主库,哪些操作使用从库。

package com.example.annotation;

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE}) // 可以用在方法或类上
@Retention(RetentionPolicy.RUNTIME)             // 在运行时保留,便于通过反射获取
@Documented
public @interface DataSource {
    String value() default "master"; // 默认使用主库
}

注解参数说明

  • @Target:定义注解的使用位置,ElementType.METHOD 表示可以作用于方法,ElementType.TYPE 表示可以作用于类。

  • @Retention:指定注解的保留策略为 RUNTIME,即该注解会保留到运行时,并且可以通过反射获取,这样切面类可以在运行时识别注解。

  • value:注解的属性,用来指定数据源类型,默认为 "master",表示主库。我们可以在使用注解时,通过设置 @DataSource("slave") 来指定从库。


3.2 创建 DataSourceAspect 切面类(根据注解动态切换数据源)

AOP(面向切面编程)可以在方法调用前后动态地切入代码逻辑。在这里,我们编写一个 AOP 切面,用于在方法调用之前,根据 @DataSource 注解的值设置数据源。在方法调用之后,清除数据源的标识,以确保不会影响后续操作。

package com.example.aspect;

import com.example.annotation.DataSource;
import com.example.config.DynamicDataSource;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DataSourceAspect {

    // 定义切点:匹配带有 @DataSource 注解的方法或类
    @Pointcut("@annotation(com.example.annotation.DataSource) || @within(com.example.annotation.DataSource)")
    public void dataSourcePointCut() {
    }

    // 在方法执行前,根据注解的值切换数据源
    @Before("dataSourcePointCut()")
    public void before(JoinPoint point) {
        String dataSource = "master"; // 默认使用主库
        Class<?> targetClass = point.getTarget().getClass();
        MethodSignature signature = (MethodSignature) point.getSignature();
        
        // 获取方法上的 @DataSource 注解
        DataSource ds = signature.getMethod().getAnnotation(DataSource.class);
        if (ds != null) {
            dataSource = ds.value();
        } else if (targetClass.isAnnotationPresent(DataSource.class)) {
            // 如果方法上没有注解,则读取类上的 @DataSource 注解
            ds = targetClass.getAnnotation(DataSource.class);
            if (ds != null) {
                dataSource = ds.value();
            }
        }

        // 设置当前线程的数据源标识
        DynamicDataSource.setDataSource(dataSource);
    }

    // 在方法执行后,清除数据源标识
    @After("dataSourcePointCut()")
    public void after(JoinPoint point) {
        DynamicDataSource.clearDataSource();
    }
}

切面类代码详解

  • @Pointcut:定义切点 dataSourcePointCut,用于匹配所有带有 @DataSource 注解的方法或类。这意味着我们可以在方法上、类上使用 @DataSource 来控制数据源选择。

  • @Before:在目标方法执行之前触发 before 方法。

    • MethodSignature:通过 MethodSignature 可以获取方法的注解。

    • dataSource:默认值为 "master"(主库)。首先检查方法上的 @DataSource 注解,如果没有找到,再检查类上的 @DataSource 注解。

    • DynamicDataSource.setDataSource(dataSource):根据注解值设置当前线程使用的数据源(主库或从库)。这使得后续的数据库操作将根据注解指定的数据源执行。

  • @After:在目标方法执行后触发 after 方法,用于清除当前线程的数据源标识,以避免线程复用时对其他请求产生干扰。DynamicDataSource.clearDataSource() 方法会移除当前线程的数据源标识。

4.在业务代码中使用

4.1 使用 @DataSource 注解

在业务代码中,可以在需要使用主库或从库的业务方法上添加 @DataSource 注解。默认情况下,@DataSource 注解的 value 属性是 "master",表示主库。我们可以在查询类方法上标记 @DataSource("slave") 以使用从库,从而实现读写分离。

4.1.1.在服务层使用 @DataSource 注解

假设我们有一个 UserService 服务类,该类提供了查询用户列表和保存用户的功能。我们可以通过 @DataSource 注解指定使用的数据库。

import com.example.annotation.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    // 使用从库(slave)进行读取操作
    @DataSource("slave")
    public List<User> getUsers() {
        // 数据库查询操作,这里会通过切面选择从库
        return userRepository.findAll();
    }

    // 使用主库(master)进行写入操作
    @DataSource("master")
    public void saveUser(User user) {
        // 数据库写入操作,这里会通过切面选择主库
        userRepository.save(user);
    }
}

示例说明

  • getUsers 方法:由于标注了 @DataSource("slave") 注解,因此在执行 getUsers 方法时会选择从库作为当前数据源,从而使查询操作通过从库完成,减轻主库压力。

  • saveUser 方法:标注了 @DataSource("master") 注解,因此在执行 saveUser 方法时会选择主库作为当前数据源,从而保证数据写入操作在主库上进行,确保数据的一致性。

4.1.2.在 DAO 层(数据持久层)使用 @DataSource 注解

假设我们将数据源控制进一步细化到 DAO 层。例如,在 UserRepository 中的查询和保存方法上分别指定数据源。

import com.example.annotation.DataSource;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    // 使用从库(slave)进行读取操作
    @DataSource("slave")
    List<User> findByName(String name);

    // 使用主库(master)进行写入操作
    @DataSource("master")
    User save(User user);
}

示例说明

  • findByName 方法:该方法标注了 @DataSource("slave"),因此在调用 findByName 时,数据查询操作将通过从库完成。

  • save 方法:该方法标注了 @DataSource("master"),确保数据写入操作始终通过主库进行,保证了数据的完整性。

注意:将 @DataSource 注解放在服务层和 DAO 层都会生效。实际应用中可以根据业务逻辑的复杂程度来决定在哪一层实现数据源的切换。

4.2 确保读操作走从库,写操作走主库

通过在业务方法中使用 @DataSource 注解,可以实现以下的逻辑:

1.读操作走从库:在查询数据的方法上添加 @DataSource("slave"),使这些方法通过从库执行。

  • 从库通常用于处理读操作。这样可以分担主库的负载,提升系统的读取性能。
  • 因为从库的数据来自主库的复制,存在一定延迟,所以一般不用于强一致性要求的场景,而适合对实时性要求较低的查询场景。

2.写操作走主库:在插入、更新、删除数据的方法上添加 @DataSource("master"),确保这些操作通过主库执行。

  • 主库是数据的源头,负责处理写入、更新等操作,以确保数据的准确性和一致性。
  • 这样可以避免因为复制延迟导致的数据不一致问题。

示例:在 ProductService 中实现读写分离

import com.example.annotation.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

    // 使用从库读取产品列表
    @DataSource("slave")
    public List<Product> getAllProducts() {
        // 从库执行查询
        return productRepository.findAll();
    }

    // 使用主库保存产品信息
    @DataSource("master")
    public void addProduct(Product product) {
        // 主库执行插入操作
        productRepository.save(product);
    }

    // 更新产品信息,使用主库
    @DataSource("master")
    public void updateProduct(Product product) {
        // 主库执行更新操作
        productRepository.save(product);
    }

    // 删除产品信息,使用主库
    @DataSource("master")
    public void deleteProductById(Long productId) {
        // 主库执行删除操作
        productRepository.deleteById(productId);
    }
}

代码解释:

  • getAllProducts() 方法

    • 使用 @DataSource("slave") 注解,从库将执行该方法中的查询操作。
    • 适用于读取类操作,减轻主库压力。
  • addProduct()、updateProduct() 和 deleteProductById() 方法

    • 这些方法使用 @DataSource("master") 注解,因此会走主库。
    • 适用于写操作(新增、修改、删除),确保数据的强一致性和实时性。

4.3 注意事项

在实现读写分离时,以下几点需要注意:

  • 主从数据一致性:从库的数据源于主库的复制,所以存在一定的延迟。对于不允许数据延迟的操作,建议强制使用主库。比如订单支付或库存更新等操作,通常对实时性要求较高,应直接走主库。

  • 事务管理:在事务中进行读操作,可能会导致数据源切换失效,因为在事务中默认会使用主库。这种情况下,可以使用 @Transactional(readOnly = true) 标记方法为只读事务,让 Spring 在事务中仍使用从库。

  • 线程安全:因为数据源切换是基于 ThreadLocal 实现的,所以多线程环境下可以安全地设置和切换数据源标识。然而,如果有异步操作,可能会导致数据源信息传递失效,需要特别注意。

5.测试和验证

完成 Spring Boot 项目中多数据源的配置之后,需要测试项目是否能够正确地实现读写分离。主要步骤包括:检查 MySQL 主库和从库的服务状态、启动项目、编写并运行测试类,以及验证主从数据库的数据同步情况。


5.1 检查 MySQL 主库和从库的服务状态

要确保 Spring Boot 项目能够连接到主从数据库,首先需要确认 MySQL 主库和从库的服务状态。如果主库和从库未启动,Spring Boot 应用将无法连接数据库。

5.1.1 使用命令行检查 MySQL 服务状态

在主库和从库的服务器上,可以使用以下命令检查 MySQL 服务状态:

# 检查 MySQL 服务是否正在运行
sudo systemctl status mysql

如果显示 active (running),则表示 MySQL 服务正在运行。

5.1.2 使用命令行启动 MySQL 服务

如果 MySQL 服务未运行,可以使用以下命令启动:

# 启动 MySQL 服务
sudo systemctl start mysql

启动服务后,可以再次使用 status 命令检查服务状态,确保 MySQL 服务已启动。

注意:如果主库和从库在不同的服务器上,您需要分别在主库服务器和从库服务器上执行上述命令。


5.2 使用 IntelliJ IDEA 启动 Spring Boot 项目

在确认 MySQL 主库和从库均已启动后,可以启动 Spring Boot 项目。

5.2.1 在 IntelliJ IDEA 中启动 Spring Boot 项目

  1. 打开项目:在 IntelliJ IDEA 中打开您的 Spring Boot 项目。
  2. 定位主类:在项目的 src/main/java 目录中,找到主类(通常是带有 @SpringBootApplication 注解的类,比如 YourApplication 类)。
  3. 运行项目:右键点击主类文件,选择“Run ‘YourApplication’”(运行 YourApplication)选项。IDEA 将会在控制台显示启动日志。

5.2.2 检查控制台输出

项目启动后,检查 IDEA 控制台的日志输出,确保没有报错信息。如果看到类似以下内容,则说明项目启动成功:

INFO 12345 --- [main] com.example.YourApplication       : Started YourApplication in 3.456 seconds (JVM running for 4.123)

5.3 编写测试类,测试读写操作是否按照预期走对应的数据源

在项目启动后,我们可以编写测试类,通过测试 Spring Boot 项目中是否实现了主从分离(读操作走从库、写操作走主库)。

5.3.1 创建测试类

在项目的 src/test/java 目录下,创建一个新的测试类,例如 UserServiceTest,用于测试服务类中的读写方法。以下是测试类的示例代码:

import com.example.service.UserService;
import com.example.model.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class UserServiceTest {

    @Autowired
    private UserService userService;

    // 测试读操作
    @Test
    public void testReadOperation() {
        // 调用读取用户的方法
        System.out.println("Executing read operation (should use slave database)...");
        userService.getUsers();
    }

    // 测试写操作
    @Test
    public void testWriteOperation() {
        // 创建一个新的用户
        User user = new User();
        user.setName("Test User");
        user.setEmail("testuser@example.com");

        // 调用保存用户的方法
        System.out.println("Executing write operation (should use master database)...");
        userService.saveUser(user);
    }
}

代码说明

  • @SpringBootTest:该注解用于在测试类中启动 Spring Boot 上下文。它会自动加载配置文件和应用上下文,确保测试类中可以注入依赖。
  • @Autowired:注入 UserService,以便在测试方法中调用 getUserssaveUser 方法。
  • @Test:每个测试方法都使用 @Test 注解,表明这是一个测试用例,测试框架会自动运行带有 @Test 的方法。

测试类的测试方法

  • testReadOperation:测试从库的读取操作。

    • 该方法会调用 userService.getUsers(),这应该会使用从库来执行查询操作。我们可以在日志中确认是否成功走到了从库。
  • testWriteOperation:测试主库的写入操作。

    • 该方法会创建一个新用户并调用 userService.saveUser(user),应该会使用主库来执行插入操作。可以在日志中查看是否成功使用主库。

5.3.3 在 IntelliJ IDEA 中运行测试

在 IntelliJ IDEA 中可以通过以下步骤运行测试类:

  • 运行单个测试方法:在 testReadOperationtestWriteOperation 方法上右键点击,选择“Run ‘testReadOperation’”或“Run ‘testWriteOperation’”来运行特定测试。
  • 运行整个测试类:在 UserServiceTest 类名上右键点击,选择“Run ‘UserServiceTest’”,以运行所有测试方法。

5.3.4 检查测试输出

在控制台中查看输出信息。如果您在 DynamicDataSource 类中添加了日志信息,可以看到类似以下的日志:

Switching to data source: slave
Executing read operation (should use slave database)...
Switching to data source: master
Executing write operation (should use master database)...
  • 读操作日志:如果 testReadOperation 的日志显示 Switching to data source: slave,说明读操作成功走了从库。
  • 写操作日志:如果 testWriteOperation 的日志显示 Switching to data source: master,说明写操作成功走了主库。

5.4 检查主从数据库的数据同步

在验证读写操作走了正确的数据源后,还需要检查主从数据库的数据同步情况,以确认主库的数据更改是否成功同步到从库。

5.4.1 执行写入操作,检查数据同步

  1. 运行写入测试方法:通过 testWriteOperation 或直接调用服务中的写入方法,向主库插入新数据。

  2. 在主库中检查数据:连接到主库,执行以下查询,确认数据是否成功插入:

    SELECT * FROM your_database.users WHERE name = 'Test User';
    
  3. 在从库中检查数据同步:稍等片刻后,连接到从库,执行相同的查询,检查数据是否已同步:

    SELECT * FROM your_database.users WHERE name = 'Test User';
    

    如果从库中也能查询到这条记录,则表明主库的数据成功同步到了从库。

5.4.2 执行更新操作,检查数据同步

  1. 运行更新操作:在主库中更新记录的某个字段,例如通过 userService.updateUser(user) 方法更新 email 字段(假设该方法存在),或直接在主库执行更新 SQL 语句:

    UPDATE your_database.users SET email = 'updateduser@example.com' WHERE name = 'Test User';
    
  2. 在从库中检查数据同步:稍等片刻后,在从库执行相同的查询,确认 email 字段是否已更新为 'updateduser@example.com'

5.4.3 执行删除操作,检查数据同步

  1. 运行删除操作:通过 userService.deleteUserById(userId) 方法(假设存在)或直接在主库执行删除语句:

    DELETE FROM your_database.users WHERE name = 'Test User';
    
  2. 在从库中检查数据同步:在从库中执行以下查询,确认数据是否已同步删除:

    SELECT * FROM your_database.users WHERE name = 'Test User';
    

    如果查询结果为空,则表明删除操作已成功同步到从库。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/918287.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python绘制雪花

文章目录 系列目录写在前面技术需求完整代码代码分析1. 代码初始化部分分析2. 雪花绘制核心逻辑分析3. 窗口保持部分分析4. 美学与几何特点总结 写在后面 系列目录 序号直达链接爱心系列1Python制作一个无法拒绝的表白界面2Python满屏飘字表白代码3Python无限弹窗满屏表白代码4…

2023年MathorCup数学建模B题城市轨道交通列车时刻表优化问题解题全过程文档加程序

2023年第十三届MathorCup高校数学建模挑战赛 B题 城市轨道交通列车时刻表优化问题 原题再现&#xff1a; 列车时刻表优化问题是轨道交通领域行车组织方式的经典问题之一。列车时刻表规定了列车在每个车站的到达和出发&#xff08;或通过&#xff09;时刻&#xff0c;其在实际…

AntFlow 0.11.0版发布,增加springboot starter模块,一款设计上借鉴钉钉工作流的免费企业级审批流平台

AntFlow 0.11.0版发布,增加springboot starter模块,一款设计上借鉴钉钉工作流的免费企业级审批流平台 传统老牌工作流引擎比如activiti,flowable或者camunda等虽然功能强大&#xff0c;也被企业广泛采用&#xff0c;然后也存着在诸如学习曲线陡峭&#xff0c;上手难度大&#x…

构建SSH僵尸网络

import argparse import paramiko# 定义一个名为Client的类&#xff0c;用于表示SSH客户端相关操作 class Client:# 类的初始化方法&#xff0c;接收主机地址、用户名和密码作为参数def __init__(self, host, user, password):self.host hostself.user userself.password pa…

小白快速上手 labelme:新手图像标注详解教程

前言 本教程主要面向初次使用 labelme 的新手&#xff0c;详细介绍了如何在 Windows 上通过 Anaconda 创建和配置环境&#xff0c;并使用 labelme 进行图像标注。 1. 准备工作 在开始本教程之前&#xff0c;确保已经安装了 Anaconda。可以参考我之前的教程了解 Anaconda 的下…

AB矩阵秩1乘法,列乘以行

1. AB矩阵相乘 2. 代码测试 python 代码 #!/usr/bin/env python # -*- coding:utf-8 -*- # FileName :ABTest.py # Time :2024/11/17 8:37 # Author :Jason Zhang import numpy as np from abc import ABCMeta, abstractmethodnp.set_printoptions(suppressTrue, pr…

JS学习日记(jQuery库)

前言 今天先更新jQuery库的介绍&#xff0c;它是一个用来帮助快速开发的工具 介绍 jQuery是一个快速&#xff0c;小型且功能丰富的JavaScript库&#xff0c;jQuery设计宗旨是“write less&#xff0c;do more”&#xff0c;即倡导写更少的代码&#xff0c;做更多的事&#xf…

stm32下的ADC转换(江科协 HAL版)

十二. ADC采样 文章目录 十二. ADC采样12.1 ADC的采样原理12.2 STM32的采样基本过程1.引脚与GPIO端口的对应关系2.ADC规则组的四种转换模式(**)2.2 关于转换模式与配置之间的关系 12.3 ADC的时钟12.4 代码实现(ADC单通道 & ADC多通道)1. 单通道采样2. 多通道采样 19.ADC模数…

124. 二叉树中的最大路径和【 力扣(LeetCode) 】

文章目录 零、原题链接一、题目描述二、测试用例三、解题思路四、参考代码 零、原题链接 124. 二叉树中的最大路径和 一、题目描述 二叉树中的 路径 被定义为一条节点序列&#xff0c;序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径…

【安全科普】NUMA防火墙诞生记

一、我为啥姓“NUMA” 随着网络流量和数据包处理需求的指数增长&#xff0c;曾经的我面对“高性能、高吞吐、低延迟”的要求&#xff0c;逐渐变得心有余而力不足。 多CPU技术应运而生&#xff0c;SMP&#xff08;对称多处理&#xff09;和NUMA&#xff08;非一致性内存访问&a…

免费送源码:Java+Springboot+MySQL Springboot多租户博客网站的设计 计算机毕业设计原创定制

Springboot多租户博客网站的设计 摘 要 博客网站是当今网络的热点&#xff0c;博客技术的出现使得每个人可以零成本、零维护地创建自己的网络媒体&#xff0c;Blog站点所形成的网状结构促成了不同于以往社区的Blog文化&#xff0c;Blog技术缔造了“博客”文化。本文课题研究的“…

数字IC后端实现之Innovus specifyCellEdgeSpacing和ICC2 set_placement_spacing_rule的应用

昨天帮助社区IC训练营学员远程协助解决一个Calibre DRC案例。通过这个DRC Violation向大家分享下Innovus和ICC2中如何批量约束cell的spacing rule。 数字IC后端手把手实战教程 | Innovus verify_drc VIA1 DRC Violation解析及脚本自动化修复方案 下图所示为T12nm A55项目的Ca…

IntelliJ+SpringBoot项目实战(七)--在SpringBoot中整合Redis

Redis是项目开发中必不可少的缓存工具。所以在SpringBoot项目中必须整合Redis。下面是Redis整合的步骤&#xff1a; &#xff08;1&#xff09;因为目前使用openjweb-sys作为SpringBoot的启动应用&#xff0c;所以在openjweb-sys模块的application-dev.yml中增加配置参数&…

深挖C++赋值

详解赋值 const int a 10; int b a;&a 0x000000b7c6afef34 {56496} &a 0x000000b7c6afef34 {10} 3. &b 0x000000b7c6afef54 {10} 总结&#xff1a; int a 10 是指在内存中&#xff08;栈&#xff09;中创建一个int &#xff08;4 byte&#xff09;大小的空间…

java八股-jvm入门-程序计数器,堆,元空间,虚拟机栈,本地方法栈,类加载器,双亲委派,类加载执行过程

文章目录 PC Register堆虚拟机栈方法区(Metaspace元空间双亲委派机制类加载器 类装载的执行过程 PC Register 程序计数器&#xff08;Program Counter Register&#xff09;是 Java 虚拟机&#xff08;JVM&#xff09;中的一个组件&#xff0c;它在 JVM 的内存模型中扮演着非常…

11.12机器学习_特征工程

四 特征工程 1 特征工程概念 特征工程:就是对特征进行相关的处理 一般使用pandas来进行数据清洗和数据处理、使用sklearn来进行特征工程 特征工程是将任意数据(如文本或图像)转换为可用于机器学习的数字特征,比如:字典特征提取(特征离散化)、文本特征提取、图像特征提取。 …

STL序列式容器之list

相较于vector的连续性空间&#xff0c;list相对比较复杂&#xff1b;list内部使用了双向环形链表的方式对数据进行存储&#xff1b;list在增加元素时&#xff0c;采用了精准的方式分配一片空间对数据及附加指针等信息进行存储&#xff1b; list节点定义如下 template<clas…

算法日记 26-27day 贪心算法

接下来的题目有些地方比较相似。需要注意多个条件。 题目&#xff1a;分发糖果 135. 分发糖果 - 力扣&#xff08;LeetCode&#xff09; n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。 你需要按照以下要求&#xff0c;给这些孩子分发糖果&#xff1a; 每…

Linux下编译MFEM

本文记录在Linux下编译MFEM的过程。 零、环境 操作系统Ubuntu 22.04.4 LTSVS Code1.92.1Git2.34.1GCC11.4.0CMake3.22.1Boost1.74.0oneAPI2024.2.1 一、安装依赖 二、编译代码 附录I: CMakeUserPresets.json {"version": 4,"configurePresets": [{&quo…

Pytest-Bdd-Playwright 系列教程(9):使用 数据表(DataTable 参数) 来传递参数

Pytest-Bdd-Playwright 系列教程&#xff08;9&#xff09;&#xff1a;使用 数据表&#xff08;DataTable 参数&#xff09; 来传递参数 前言一、什么是 datatable 参数&#xff1f;Gherkin 表格示例 二、datatable 参数的基本使用三、完整代码和运行效果完整的测试代码 前言 …