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主从复制内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
我想要获取技术服务或软件
服务范围:MySQL、ORACLE、SQLSERVER、MongoDB、PostgreSQL 、程序问题
服务方式:远程服务、电话支持、现场服务,沟通指定方式服务
技术标签:数据恢复、安装配置、数据迁移、集群容灾、异常处理、其它问题
本站部分文章参考或来源于网络,如有侵权请联系站长。
数据库远程运维 MySQL主从复制的原理图解及Java语言示例使用
相关文章
- MySQL安装图解:从零开始 ~ 一步一步了解!(mysql安装图解)
- 解决Java程序连接MySQL数据库的方法(java链接mysql数据库)
- MySQL 时间段操作指南(mysql时间段)
- 数据如何使用Java读取MySQL数据(java读取mysql)
- Java封装MySQL让编程更简单(java封装mysql)
- MySQL安装:满足必要硬件及软件条件(mysql安装条件)
- MySQL与Java的无缝互联(java与mysql连接)
- MySQL时间处理:又快又准的操作技巧(mysql时间显示)
- MySQL数据库开发实践:用 Java 开发中文应用(mysql中文java)
- MySQL中掌握存储过程的简单方法(mysql中查看存储过程)
- MySQL驱动程序:Java集成简易操作(mysql的java驱动)
- MySQL官方网站:进入超级数据库世界(mysql官方网站)
- MySQL安装成功的测试经验分享(测试mysql安装成功)
- 深入探索MySQL自定义查询方法(mysql自定义查询)
- MySQL安全退出教程|25字中文MySQL退出命令(mysql退出命令)
- MySQL轻松删除所有表:简单、快捷、高效(mysql删除所有表)
- MySQL添加表的主键:快速,简单有效(mysql给表增加主键)
- Mysql: An Illustrated Guide.(mysql图解)
- MySQL客户端安装指南:快速图解攻略(mysql客户端安装图解)
- MySQL主从架构维护与修复(mysql 主从修复)
- MySQL与Java结合,构建高效多功能缓存系统(MySQL java缓存)
- Java与MySQL的无缝衔接:实现高效数据操作(java中使用mysql)
- MySQL实现获取数据库时间的方法(mysql获取数据库时间)
- MySQL下载指南:手把手教你一步步图文并茂(mysql下载图解)
- MySQL表数据复制:一种快速简单的解决方案(mysql 表数据复制)
- 使用Java去连接MySQL数据库(java jdbc mysql)
- 查看MySQL表是否已被锁定(mysql查询表是否被锁)
- MySQL JOIN 原理详解,图文并茂,让你彻底搞懂(mysql中join图解)
- MySQL中的a代表什么(a在mysql中代表什么)
- MySQL 51安装图文详解(5.1mysql安装图解)
- 表百万级MySQL表,大数据挑战推动企业数据深度分析(100万mysql)
- MYSQL离线使用方法大揭秘不联网也能愉快地使用MySQL(mysql 不联网吗)