Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > MySQL 主从复制数据同步

MySQL主从复制数据同步的实现步骤

作者:阿乾之铭

MySQL主从复制是一种数据同步技术,通过将数据从主数据库服务器复制到一个或多个从数据库服务器来实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一、什么是 MySQL 的主从复制

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

二、具体流程

1. 主库记录写操作

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

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

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

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

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

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

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

5. 持续复制和同步

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

三、作用和好处

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

1. 电商平台

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

2. 社交媒体或内容网站

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

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

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

集成的关键要素:

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  # 需要同步的数据库名称,多个数据库可添加多行

注意:如果需要同步多个数据库,可以多次添加 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;

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

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

SHOW MASTER STATUS;

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

+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000001 |      154 | your_database|                  |
+------------------+----------+--------------+------------------+

建议:在生产环境中,执行 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                 # 设置只读模式,防止误操作

1.2.2 重启 MySQL 服务

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

# Linux 系统
sudo systemctl restart mysqld

# Windows 系统
net stop mysql
net start mysql

1.2.3 配置从库连接到主库

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

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 值

1.2.4 启动复制进程

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

START SLAVE;

1.2.5 查看从库的复制状态

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

SHOW SLAVE STATUS\G

如果 Slave_IO_Running 或 Slave_SQL_Running 为 No,请查看 Last_IO_Error 或 Last_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;

1.3.3 验证实时同步效果

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

1.4 故障排查

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

1 从库无法连接到主库

2 权限问题

3 主从版本兼容性问题

4 日志位置不正确

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

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

2.1 添加必要的依赖

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

在项目的 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=从库密码

说明

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();
    }
}

说明

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();
    }
}

代码详解

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;
}

代码详解

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;
}

代码详解

通过配置 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());
}

代码详解

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"; // 默认使用主库
}

注解参数说明

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();
    }
}

切面类代码详解

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);
    }
}

示例说明

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);
}

示例说明

注意:将 @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);
    }
}

代码解释:

4.3 注意事项

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

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 项目

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);
    }
}

代码说明

测试类的测试方法

5.3.3 在 IntelliJ IDEA 中运行测试

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

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)...

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

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

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

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

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

到此这篇关于MySQL主从复制数据同步的实现步骤的文章就介绍到这了,更多相关MySQL 主从复制数据同步内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

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