MySQL主从复制的原理图解及Java语言示例使用

目录
  • 概述
  • 主从复制原理
  • 主从复制模式
  • 主从同步实战
  • 总结

概述

实际生产的过程中为了实现数据库的高可用,不会只有一个数据库节点。至少会搭建主从复制的数据库架构,从库可以作为主库的数据备份,以免主数据库损坏的情况下丢失数据;当访问量增加的时候可以作为读节点承担部分流量等。下面就进行从零开始搭建MySQL的主从架构。

主从复制原理

以MySQL一主两从架构为为例,也就是一个master节点下有两个slave节点,在这套架构下,写操作统一交给master节点,读请求交给slave节点处理。

为了保证master节点和slave节点数据一致,在master节点写入数据后,会同时将数据复制到对应的slave节点。主从复制数据的过程中会用到三个线程,master节点上的binlog dump线程,slave节点的I\O线程和SQL线程。

主从复制的核心流程:

  • 当master节点接收到一个写请求时,这个写请求可能是增删改操作,此时会把写请求的操作都记录到binlog日志中。
  • master节点会把数据赋值给slave节点,如图中的两个slave节点。这个过程首先得要每个slave节点连接到master节点上,当slave节点连接到master节点上时,master节点会为每一个slave节点分别创建一个binlog dump线程,用于向每个slave节点发送binlog日志。
  • 此时,binlog dump线程会读取master节点上的binlog日志,然后将binlog日志发送给slave节点上的I/O线程。
  • slave几点上的I/O线程接收到binlog日之后,会将binlog日志先写入到本地的relaylog中,relaylog中就保存了master的binlog日志。
  • 最后,slave节点上的SQL线程会读取relaylog中的biinlog日志,将其解析成具体的增删改操作,把这些在master节点上进行过的操作,重新在slave节点上也重做一遍,打到数据还原的效果,这样就可以保证master节点和slave节点的数据一致性了。

主从复制模式

MySQL的主从复制模式分为:全同步复制,异步复制,半同步复制,增强半同步复制。

全同步复制

全同步复制,就是当主库执行完一个事物之后,要求所有的从库也都必须执行完该事务,才可以返回处理结果给客户端;因此虽然全同步复制数据一致性得到保证了,但是主库完成一个事物需要等待所有从库也完成,性能就比较低了。

异步复制

异步复制,当主库提交事务后会通知binlog dump线程发送binlog日志给从库,一旦binlog dump线程将binlog日志发送给从库之后,不需要等到从库也同步完成事务,主库就会讲处理结果返回给客户端。

因为主库只管自己执行完事务,就可以将处理结果返回给客户端,而不用关系从库是否执行完事务,这就可能导致短暂的主从数据不一致的问题了,比如刚在主库插入的数据,如果马上在从库查询就可能查询不到。

当主库提交食物后,如果宕机挂掉了,此时可能binlog还没来得及同步给从库,这时候如果为了回复故障切换主从节点的话,就会出现数据丢失的问题,所以异步复制虽然性能高,但数据一致性上是比较弱的。

MySQL默认采用的是异步复制模式。

半同步复制

半同步复制就是在同步复制和异步中做了折中选择,我们可以结合着MySQL官网来看下是半同步和主从复制的过程。

当主库提交事务后,至少还需要一个从库返回接收到binlog日志,并成功写入到relaylog的消息,这个的时候,主库才会讲处理结果返回给客户端。

相比前两种复制方式,半同步复制较好地兼顾了数据一致性以及性能损耗的问题。

同时,半同步复制也存在以下几个问题:

  • 半同步复制的性能,相比异步复制而言有所下降,因为需要等到等待至少一个从库确认接收到binlog日志的响应,所以新能上是有所损耗的。
  • 主库等待从库响应的最大时长我们是可以配置的,如果超过了我们配置的事件,半同步复制就会变成异步复制,那么,异步复制的问题同样也就出现了。
  • 在MySQL5.7.2之前的版本中,半同步复制存在幻读问题。当主库成功提交事务并处于等待从库确认的过程中,这个时候,从库都还没来得及返回处理结果给客户端,但因为主库存储引擎内部已经提交事务了,所以,其他客户端是可以到主库中读到数据的。但是,如果下一秒主库宕机,下次请求过来只能读取从库,因为从库还没有从主库同步数据,所以从库中读取不到这条数据了,和上一次读取数据的结果相比,就造成了幻读的现象。

增强半同步复制

增强半同步复制是MySQL5.7.2后的版本对半同步复制做的一个改进,原理几乎是一样的,主要是解决幻读的问题。

主库配置了参数rpl_semi_sync_master_wait_point=AFTER_SYNC后,主库在存储引擎提交事务前,必须先首都哦啊从库数据同步完成的确认信息后,才能提交事务,以此来解决幻读问题。

主从同步实战

准备数据源

config/datasource.properties

# masters
spring.datasource.masters.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.masters.url=jdbc:mysql://192.168.1.111:3306/monomer_order?useUnicode=true&characterEncoding=utf8&useSSL=false&autoReconnect=true&zeroDateTimeBehavior=convertToNull
spring.datasource.masters.username=root
spring.datasource.masters.password=123456
# slaves
spring.datasource.slaves[0].driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.slaves[0].url=jdbc:mysql://192.168.1.112:3306/monomer_order?useUnicode=true&characterEncoding=utf8&useSSL=false&autoReconnect=true&zeroDateTimeBehavior=convertToNull
spring.datasource.slaves[0].username=root
spring.datasource.slaves[0].password=123456

配置数据源

package com.xinxin.order.context.config;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.util.CollectionUtils;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@Data
@Configuration
@PropertySource("classpath:config/datasource.properties")
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceConfig {
    /**
     * 主库数据源信息
     */
    private Map<String, String> masters;
    /**
     * 从库数据源信息
     */
    private List<Map<String, String>> slaves;
    @SneakyThrows
    @Bean
    public DataSource masterDataSource() {
        log.info("masters:{}", masters);
        if (CollectionUtils.isEmpty(masters)) {
            throw new Exception("主库数据源不能为空");
        }
        return DruidDataSourceFactory.createDataSource(masters);
    }
    @SneakyThrows
    @Bean
    public List<DataSource> slaveDataSources() {
        if (CollectionUtils.isEmpty(slaves)) {
            throw new Exception("从库数据源不能为空");
        }
        final ArrayList<DataSource> dataSources = new ArrayList<>();
        for (Map<String, String> slaveProperties : slaves) {
            log.info("slave:{}", slaveProperties);
            dataSources.add(DruidDataSourceFactory.createDataSource(slaveProperties));
        }
        return dataSources;
    }
    @Bean
    @Primary
    @DependsOn({"masterDataSource", "slaveDataSources"})
    public DataSource routingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                        @Qualifier("slaveDataSources") List<DataSource> slaveDataSources) {
        final Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceContextHolder.MASTER, masterDataSource);
        for (int i = 0; i < slaveDataSources.size(); i++) {
            targetDataSources.put(DataSourceContextHolder.SLAVE + i, slaveDataSources.get(i));
        }
        final DataSourceRouter dataSourceRouter = new DataSourceRouter();
        dataSourceRouter.setTargetDataSources(targetDataSources);
        dataSourceRouter.setDefaultTargetDataSource(masterDataSource);
        return dataSourceRouter;
    }
    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(
            @Qualifier("routingDataSource") DataSource routingDataSource) {
        return new DataSourceTransactionManager(routingDataSource);
    }
}

数据源上下文切换

package com.xinxin.order.context.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@Slf4j
public class DataSourceContextHolder {
    public static final String MASTER = "master";
    public static final String SLAVE = "slave";
    private static ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    public static void setDatasourceType(String dataSourceType) {
        if (StringUtils.isBlank(dataSourceType)) {
            log.error("dataSourceType为空");
        }
        log.info("设置dataSource: {}", dataSourceType);
        CONTEXT_HOLDER.set(dataSourceType);
    }
    public static String getDataSourceType() {
        return CONTEXT_HOLDER.get() == null ? MASTER : CONTEXT_HOLDER.get();
    }
    public static void remove() {
        CONTEXT_HOLDER.remove();
    }
}

数据源路由实现类

package com.xinxin.order.context.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
@Slf4j
public class DataSourceRouter extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        log.info("当前数据源为: {}", DataSourceContextHolder.getDataSourceType());
        return DataSourceContextHolder.getDataSourceType();
    }
}

数据源切换注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {
    String value() default DataSourceContextHolder.MASTER;
}

动态数据源切换切面

package com.xinxin.order.aspect;
import com.xinxin.order.annotation.ReadOnly;
import com.xinxin.order.context.config.DataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class DynamicDataSourceAspect implements Ordered {
    @Before(value = "execution(* *(..))&& @annotation(readOnly)")
    public void before(JoinPoint joinPoint, ReadOnly readOnly) {
        log.info(joinPoint.getSignature().getName() + "走从库");
        DataSourceContextHolder.setDatasourceType(DataSourceContextHolder.SLAVE);
    }
    @After(value = "execution(* *(..))&& @annotation(readOnly)")
    public void after(JoinPoint joinPoint, ReadOnly readOnly) {
        log.info(joinPoint.getSignature().getName() + "清除数据源");
        DataSourceContextHolder.remove();
    }
    @Override
    public int getOrder() {
        return 0;
    }
}

总结

项目整合读写分离主要是通过收到注入数据源,并通过拦截器设置当前线程的数据源类型,需要使用数据源的地方会通过数据源路由器读取当前线程的数据源类型后返回实际的数据源进行数据库的操作。

到此这篇关于Java MySQL主从复制的原理图解及示例使用的文章就介绍到这了,更多相关Java MySQL主从复制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • MySQL数据库主从复制与读写分离

    目录 一.主从复制 主从复制三线程 主从复制的过程: 主从复制的策略: 主从复制高延迟 二.读写分离 读写分离概念 读写分离原因与场景 总结 一.主从复制        主从复制:在实际的生产中,为了解决Mysql的单点故障以及提高MySQL的整体服务性能,一般都会采用主从复制.即:对数据库中的数据.语句做备份. 主从复制三线程        Mysql的主从复制中主要有三个线程:master(binlog dump thread).slave(I/O thread .SQL thread),M

  • MySQL 配置主从复制实践分享

    目录 一.检测通信 二.master配置 1. 开启二进制日志 2. 创建一个用于主从库通信用的账号 3. 获取binlog的日志文件名和position 三.slave配置 1. 配置全局唯一的server-id 2. 使用master创建的账户读取binlog同步数据 3. 开启slave服务 四.配置中可能出现的问题 1. 网络连接问题 2. binlog的position问题 3. SQL线程出错 一.检测通信 查看主库(master)和从库(slave)的ip地址,并检测是否可以通信

  • MySQL 主从复制数据不一致的解决方法

    目录 1. 准备工作 1.1 主机配置 1.2 从机配置 2. 数据不一致问题 3. 原因分析 4. 问题解决 5. 小结 今天来说说 MySQL 主从复制数据不一致的问题,通过几个具体的案例,来向小伙伴们展示 binlog 不同 format 之间的区别. 1. 准备工作 以下配置基于 Docker. 我这里有一张简单的图向大伙展示 MySQL 主从的工作方式: 这里,我们准备两台机器: 主机:10.3.50.27:33061 从机:10.3.50.27:33062 1.1 主机配置 主机的配

  • MySQL的主从复制原理详细分析

    目录 前言 一.主从复制概念 二.读写分离的概念 三.主库和从库 1. 主库 2. 从库 四.主从复制的流程 五.主从复制效果展示 前言 在实际生产环境中,如果对mysql数据库的读和写都在一台数据库服务器中操作,无论是在安全性.高可用性,还是高并发等各个方面都是不能满足实际需求的,一般要通过主从复制的方式来同步数据,再通过读写分离来提升数据库的并发负载能力. 一.主从复制概念 主从复制是MySQL提供的基本的技术,主从复制的流程:binlog二进制日志(除了查询其他的更改相关的操作都会记录在b

  • MySQL实现配置主从复制项目实践

    目录 一.检测通信 二.master配置 1. 开启二进制日志 2. 创建一个用于主从库通信用的账号 3. 获取binlog文件名和position 三.slave配置 1. 配置全局唯一的server-id 2. 使用master创建的账户读取binlog同步数据 3. 开启slave服务 四.配置中可能出现的问题 1. 网络连接问题 2. binlog的position问题 3. SQL线程出错 一.检测通信 查看master(centos7)和slave(win10)的ip地址,并检测是否

  • MySQL主从复制原理详情

    目录 前言: 一.为什么需要主从复制? 二.什么是mysql的主从复制? 三.mysql复制原理 具体步骤 四.mysql主从同步延时分析 五.主从复制的配置 1.基础设置准备 2.安装mysql数据库 3.在两台数据库中分别创建数据库 4.在主(node1)服务器进行如下配置: 5.配置从(node2)服务器登录主服务器的账号授权 6.从(node2)服务器的配置 7.重启主服务器的mysqld服务 8.重启从服务器并进行相关配置 前言: 对于现在的系统来说,在业务复杂的系统中,数据库往往是应

  • MySQL主从复制的原理图解及Java语言示例使用

    目录 概述 主从复制原理 主从复制模式 主从同步实战 总结 概述 实际生产的过程中为了实现数据库的高可用,不会只有一个数据库节点.至少会搭建主从复制的数据库架构,从库可以作为主库的数据备份,以免主数据库损坏的情况下丢失数据:当访问量增加的时候可以作为读节点承担部分流量等.下面就进行从零开始搭建MySQL的主从架构. 主从复制原理 以MySQL一主两从架构为为例,也就是一个master节点下有两个slave节点,在这套架构下,写操作统一交给master节点,读请求交给slave节点处理. 为了保证

  • MySql主从复制实现原理及配置

    数据库读写分离对于大型系统或者访问量很高的互联网应用来说,是必不可少的一个重要功能.对于MySQL来说,标准的读写分离是主从模式,一个写节点Master后面跟着多个读节点,读节点的数量取决于系统的压力,通常是1-3个读节点的配置.而一般的读写分离中间件,例如Mycat的读写分离和自动切换机制,需要mysql的主从复制机制配合. 主从配置需要注意的地方 1.主DB server和从DB server数据库的版本一致 2.主DB server和从DB server数据库数据名称一致 3.主DB se

  • MySQL主从复制的原理及配置方法(比较详细)

    一.复制的原理 MySQL 复制基于主服务器在二进制日志中跟踪所有对数据库的更改(更新.删除等等).每个从服务器从主服务器接收主服务器已经记录到其二进制日志的保存的更新,以便从服务器可以对其数据拷贝执行相同的更新. 将主服务器的数据拷贝到从服务器的一个途径是使用LOAD DATA FROM MASTER语句.请注意LOAD DATA FROM MASTER目前只在所有表使用MyISAM存储引擎的主服务器上工作.并且,该语句将获得全局读锁定. MySQL 使用3个线程来执行复制功能,其中1个在主服

  • 一篇文章看懂MySQL主从复制与读写分离

    目录 引言 一.MySQL主从复制 1.MySQL的复制类型 2.MySQL主从复制的原理 3.MySQL主从复制延迟 二.MySQL读写分离 1.常见的 MySQL 读写分离分 2.MySQL 读写分离原理 三.MySQL主从复制与读写分离配置步骤 1.搭建环境 2.解决需要 3.准备阶段(关闭防火墙及控制访问机制) 4.搭建MySQL主从复制 5.搭建MySQL读写分离步骤 6.测试测试读写分离 总结 引言 企业中的业务通常数据量都比较大,而单台数据库在数据存储.安全性和高并发方面都无法满足

  • MySQL主从同步原理介绍

    概述 Mysql的Replication(复制)是一个异步的复制过程,从一个 Mysql instance(我们称之为 Master)复制到另一个Mysql instance(我们称之 Slave).在 Master 与 Slave之间的实现整个复制过程主要由三个线程来完成,其中两个线程(Sql线程和IO线程)在 Slave 端,另外一个线程(IO线程)在Master端. 主从同步需求 要实现 MySQL 的 Replication ,首先必须打开 Master 端的BinaryLog(mysq

  • 详解Docker方式实现MySql 主从复制(实践篇)

    本文实践了用Docker方式来实现基于binlog的MySql主从复制.关于MySql主从复制的原理将在下一篇中进行讲解. 一些数据的本地存储目录结构 mysql >tree -L 2 . ├── data │ ├── master01 │ └── slave01 ├── master01 │ └── master01.cnf └── slave01 └── slave01.cnf master01.cnf配置 [mysqld] log-bin=mysql-master01-bin # 使用bi

  • mysql主从复制的实现步骤

    目录 mysql主从复制 mysql主从复制的方式 mysql主从复制的原理 mysql的主从配置的具体实现方式 1. Master配置 2. Slave配置 mysql主从复制 本来是在做sharding的,可是做好之后发现数据库的主从复制还没有做,所以在配置好了之后做了些记录: mysql主从复制的方式 mysql主从复制有两种方式:基于日志(binlog).基于GTID(全局事物标示符)本博文用的是基于日志(binlog)的复制. mysql主从复制的原理 1. Master将数据记录到二

  • 全面解读MySQL主从复制,从原理到安装配置

    为什么需要主从复制? 1.在业务复杂的系统中,有这么一个情景,有一句sql语句需要锁表,导致暂时不能使用读的服务,那么就很影响运行中的业务,使用主从复制,让主库负责写,从库负责读,这样,即使主库出现了锁表的情景,通过读从库也可以保证业务的正常运作. 2.做数据的热备 3.架构的扩展.业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能. 什么是mysql的主从复制? MySQL 主从复制是指数据可以从一个MySQL数据库服务器主节

  • Mysql主从复制作用和工作原理详解

    一.什么是主从复制 主从复制,是用来建立一个和主数据库完全一样的数据库环境,称为从数据库,主数据库一般是准实时的业务数据库.在最常用的mysql数据库中,支持单项.异步赋值.在赋值过程中,一个服务器充当主服务器,而另外一台服务器充当从服务器:此时主服务器会将更新信息写入到一个特定的二进制文件中. 并会维护文件的一个索引用来跟踪日志循环.这个日志可以记录并发送到从服务器的更新中去.当一台从服务器连接到主服务器时,从服务器会通知主服务器从服务器的日志文件中读取最后一次成功更新的位置.然后从服务器会接

  • MySQL 主从复制原理与实践详解

    本文实例讲述了MySQL 主从复制原理与实践.分享给大家供大家参考,具体如下: 简介 MySQL 的主从复制又叫 Replication.AB 复制.至少需要两个 MySQL 服务(可以是同一台机器,也可以是不同机器之间进行). 比如A服务器做主服务器,B服务器做从服务器,在A服务器上进行数据的更新,通过 binlog 日志记录同步到B服务器上,并重新执行同步过来的 binlog 数据,从而达到两台服务器数据一致. MySQL 数据库的主从复制方案,与使用 scp/rsync 等命令进行的文件级

随机推荐