【实验】MySQL主从复制及读写分离

一、理论

1.1 主从复制原理图

在这里插入图片描述

1.2 主从复制文字描述

  1. master服务器写入新的数据,添加新数据的操作记录会同步写入bin-log二进制文件。
  2. slave服务器会定时对master服务器进行探测,如果发现master服务器的bin-log文件发生了改变,就会开启一个I/O线程来请求master服务器的bin-log文件的二进制事件(更改操作的语句)。
  3. master服务器会针对该I/O线程开启一个dump线程,用来发送master服务器的bin-log文件中的二进制事件给slave服务器。
  4. slave服务器接收到来自master服务器的bin-log中的二进制事件,将其先存放在slave的中继日志relay-log中。
  5. slave服务器开启sql线程,读取relay-log中的bin-log中的二进制事件,将之逐条解析成sql语句后,写入slave服务器中,从而实现master和slave的数据一致性。
  6. 执行完上述操作,I/O线程和sql线程会自动进入休眠状态,等待下一次被唤醒。
  7. 在MySQL5.7版本之后,会多出一个ACK Collector线程,该线程的作用我们一会儿会细说。

注:master发送的是bin-log中的二进制事件,也就是说,slave最终复现的内容,是主从搭建完毕后的master中所做的更改操作的内容,在主从复制没有完成前的master中的数据不会复现到slave中,即主从完成前,master库中已有的或者已更改的数据不会同步到slave服务器中,主从完成后,master的一些列更改操作才会复现到slave的库中。

1.3 关键点

  1. 三(四)个线程:
    • IO线程:用于请求master的二进制事件的线程
    • dump线程:针对IO线程开启的发送二进制事件给slave的线程
    • sql线程:slave上用于将relay-log中的二进制事件逐条解读,然后复现到数据库中的线程
    • ack collector线程:用于在半同步模式下主从复制时,收集slave发送给master的状态信息的线程
  2. 两个日志:
    • bin-log:二进制日志,在master服务器开启,用于记录更改的数据操作(记录二进制事件)
    • relay-log:中继日志:在slave服务器开启,用于接收从master服务器发送过来的二进制事件,然后由sql线程读取,复现

注:relay-log一般存放在slave服务器的系统缓存中,所以relay-log的开销很少;主从复制在slave服务器上是串行化的,即一条一条执行,所以master上的并行操作是不在slave上并行操作的。

1.4 主从复制的三个模式

  1. 异步模式:异步模式是MySQL主从复制的默认模式,master在执行完更改操作后会立刻返回结果给客户端,并不关心slave服务器是否收到二进制事件并且复现。这样很容易出现一个问题,就是如果在slave定时探测master的冷却时间内,master更改完数据后就挂了,此时就会导致master上的二进制事件无法发送到slave上,导致主从的数据不一致,即slave的数据不完整。
  2. 全同步模式:全同步模式是在master上进行更改操作时,不会立刻将结果返回给客户端,而是等待所有的slave服务器都完成同步了才会将结果返回给客户端。如果说,我们有好多个slave服务器,那么就会花费大量的时间进行数据同步,从而影响了效率和性能。
  3. 半同步模式:异步模式和全同步模式的折中方法,我个人认为在大多数生产环境中能够采用的最适合一种模式,该模式在master上进行完操作后,并不会直接返回结果给客户端,而是等待任意的一个slave服务器成功接收到二进制事件并且将其写入该slave服务器的relay-log中,master才会返回结果给客户端。这时,ack collector线程(mysql5.7版本之后)承担着将slave的状态信息反馈给master的作用。半同步模式相比异步模式增强了数据的安全性,相较于全同步模式降低了数据同步的时间。但是该时间至少是一个TCP/IP往返的时间,所以半同步模式最好在网络延迟较低的环境下使用(影响不是很大,除非特定的时间,数据吞吐量特别的大,比如双11,618)。

1.5 MySQL支持的复制类型

  1. statement:statement是基于语句的复制,即我们在master上执行的sql语句会同步到slave上执行相同的语句,在5.7版本之前,MySQL默认的就是基于statement的复制,其执行效率高,但是在高并发的场景可能会由于slave上语句执行顺序的误差而导致死锁(master并发执行大量sql语句,但slave只能串行执行,如果多张表中具有主键、外键之间的联系,再由于网络的关系,部分后执行语句先一步同步到slave服务器中,就会因为表之间的联系而导致死锁。比如有student表中的id字段为主键,grade表中的id为student.id的外键,如果student.id没有2这条数据,grade.id就无法插入2这条数据,而恰巧增加grade.id=2的语句先比增加student.id=2的语句一步到达slave,就会导致死锁,因为grade.id是依赖于student.id的,而因为grade.id先student.id一步到达,所以slave先执行grade.id,从而导致死锁)。
  2. row:基于行的复制,它是把改变的内容复制过去,而不是像statement一样再执行一遍,这样做能够很精确但是效率太低,保存的文件会更大(5.7版本后默认使用row模式)。
  3. MIXED:混合模式,即statement和row混合使用,默认采用statement,一旦发现statement无法精确复制时,就会采用row。这样做,就会变得更智能、更灵活。所以大部分情况下使用MIXED。

1.6 数据库部分问题的解决方案

  1. 主从数据不一致的解决方案

    • 忽略错误,继续同步:该方法适用于主从库数据相差不大,或者要求数据可以不完全统一的情况,数据要求不严格的情况
    • 重做主从,完全同步:该方法适用于主从库数据相差较大,或者要求数据完全统一的情况
  2. slave服务器down掉后又恢复的数据同步方案

    • 物理方法: rsync 磁盘文件同步。 使用文件恢复,主节点需要停服务
    • 主从复制: 将从节点原有库删除,通过偏移量,重新做一次主从复制
  3. 半同步复制将为异步复制以及从异步恢复半同步的情况

    • 当半同步复制发生超时(由rpl_semi_sync_master_timeout 参数控制,默认为10000ms, 即10s),会暂时关闭半同步复制,转而使用异步复制,也就是会自动降为异步工作。
    • 当malster dump 线程发送完一个事务的所有事件之后,如果在rpl_ semi_sync_master_ timeout 内,收到了从库的响应,则主从又重新恢复为半同步复制。
  4. MySQL主从复制延迟原因和优化方法

    • 原因:

      • master服务器大量并发操作,形成大量事务
      • 网络延迟
      • 硬件设备(CPU频率、内存I/O和硬盘I/O)
    • 优化方法:

      • 从库使用SSD磁盘
      • 网络优化,避免跨机房实现同步
      • 从库使用高性能主机。包括cpu强悍、内存加大。避免使用虚拟云主机,使用物理主机,这样提升了I/O方面性
      • 从库优化Mysql参数。比如增大innodb_buffer_pool_size,让更多操作在Mysql内存中完成,减少磁盘操作

二、主从复制实验

2.1 架构图

在这里插入图片描述
准备工作:

  1. master服务器:IP为192.168.41.47,系统为centos7,安装MySQL5.7
  2. slaves服务器:IP为192.168.41.48和192.168.41.71,系统为centos7,安装MySQL5.7

2.2 实验流程

1.异步复制:

-----------------------master-------------------------
yum install -y ntp   #安装ntp服务进行时间同步
vim /etc/ntp.conf    #修改ntp配置文件,添加以下配置
server 127.127.41.0   #设置本地时钟源,网段是自己的网段
fudge 127.127.41.0 stratum 8  #设置时间层级为8(限制在15)
systemctl start ntpd   #开启ntp服务
vim /etc/my.cnf   #修改数据库配置文件
server-id = 11    #修改server-id参数,每一台服务器的server-id都不能一样
log-bin=mysql-bin   #开启二进制日志
binlog_format = MIXED   #设置二进制文件的复制类型为MIXED
log-slave-updates=true   #允许slave从master复制数据时可以写入到自己的二进制日志
expire_logs_days = 7  #设置二进制日志文件过期时间,默认值为0,表示logs不过期
max_binlog_size = 500M   #设置二进制日志限制大小,如果超出给定值,日志就会发生滚动,默认值是1GB

systemctl restart mysqld   #重启mysql服务
mysql -uroot -p000000   #登录数据库
grant replication slave on *.* to 'myslave'@'192.168.41.%' identified by '123456';   为slave授权同步
 flush privileges   #刷新权限
 show master status;   #查看master的状态信息,记住File和Position字段的值
 
---------------------------slave1-------------------------
yum install -y ntp ntpdate
systemctl start ntpd   
/usr/sbin/ntpdate 192.168.41.47				#与master进行时间同步
crontab -e   #设置定时任务
*/30 * * * * /usr/sbin/ntpdate 192.168.41.47
vim /etc/my.cnf
server-id = 22	#修修改server-id参数,每一台服务器的server-id都不能一样
relay-log=relay-log-bin  #开启中继日志,从主服务器上同步日志文件记录到本地
relay-log-index=slave-relay-bin.index   #定义中继日志文件的位置和名称,一般和relay-log在同一目录
relay_log_recovery = 1   #当 slave 从库宕机后,假如 relay-log 损坏了,导致一部分中继日志没有处理,则自动放弃所有未执行的 relay-log,并且重新从 master 上获取日志,这样就保证了 relay-log 的完整性。默认情况下该功能是关闭的,将 relay_log_recovery 的值设置为 1 时, 可在 slave 从库上开启该功能,建议开启。
systemctl restart mysqld
mysql -uroot -p000000   #登录数据库
change master to 
master_host='192.168.41.47',
master_user='myslave',
master_password='123456',
master_log_file='mysql-bin.000004',
master_log_pos=603;
#配置同步,注意 master_log_file 和 master_log_pos 的值要与Master查询的一致
start slave;   #启动同步,如有报错执行 reset slave;
show slave status\G	   #查看slave状态,确保IO和SQL线程都是Yes,代表同步正常。

--------------------------slave2-----------------------
和slave1一模一样,只是在my.cnf的server-id那里改动一下,设置为33就行,id可以随便设置,只要不重复就行。

master:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由于我之前有操作失误,所以这里的file是00004,具体的二进制文件名根据自己的实际情况进行设置,还有Position。

slave1:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
slave2:
在这里插入图片描述
测试:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2.半同步复制:
半同步复制只需要在异步复制的基础上在MySQL得配置文件中加上几条配置即可。

--------------------master------------------------
vim /etc/my.cnf
plugin-load=rpl_semi_sync_master=semisync_master.so     #加载mysql半同步复制的插件
rpl_semi_sync_master_enabled=ON         #或者设置为"1",即开启半同步复制功能
rpl-semi-sync-master-timeout=1000       #超时时间为1000ms,即1s
systemctl restart mysqld
-------------------slave1、slave2-----------------------
plugin-load=rpl_semi_sync_slave=semisync_slave.so
rpl_semi_sync_slave_enabled=ON​
systemctl restart mysqld

-----------------查看半同步状态-----------------------
#master查看半同步状态
show status like 'Rpl_semi_sync_master_status';
show variables like 'rpl_semi_sync_master_timeout';#从slave查看半同步状态(如果OFF状态,需要在下一步重启IO线程后,从库半同步状态才会为ON)
show status like 'Rpl_semi_sync_slave_status';#如果salve的status为OFF,再重启从数据库上的IO线程
stop slave io_thread;
stsrt slave io_thread;#在主库查询半同步状态
show status like '%Rpl_semi%';  
参数说明:
Rpl_semi_sync_master_clients                     #半同步复制客户端的个数
 Rpl_semi_sync_master_net_avg_wait_time          #平均等待时间(默认毫秒)
 Rpl_semi_sync_master_net_wait_time              #总共等待时间
 Rpl_semi_sync_master_net_waits                  #等待次数
 Rpl_semi_sync_master_no_times                   #关闭半同步复制的次数
 Rpl_semi_sync_master_no_tx                      #表示没有成功接收slave提交的次数
 Rpl_semi_sync_master_status                     #表示当前是异步模式还是半同步模式,on为半同步
 Rpl_semi_sync_master_timefunc_failures          #调用时间函数失败的次数
 Rpl_semi_sync_master_tx_avg_wait_time           #事物的平均传输时间
 Rpl_semi_sync_master_tx_wait_time               #事物的总共传输时间
 Rpl_semi_sync_master_tx_waits                   #事物等待次数
 Rpl_semi_sync_master_wait_pos_backtraverse      #可以理解为"后来的先到了,而先来的还没有到的次数"
 Rpl_semi_sync_master_wait_sessions              #当前有多少个session因为slave的回复而造成等待
 Rpl_semi_sync_master_yes_tx                     #成功接受到slave事物回复的次数



在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

三、读写分离

3.1 什么是读写分离

读写分离,基本的原理是让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。

3.2 为什么要读写分离

数据库的写操作是比较耗时的,可能写10000条数据需要3分钟,但是,读10000条数据只需要5秒,如果不进行读写分离,就大大影响了数据库的工作效率。所以读写分离,解决的是,数据库的写入,影响了查询的效率。

3.3 什么时候要读写分离?

数据库不一定要读写分离,如果程序使用数据库较多时,而更新少,查询多的情况下会考虑使用。利用数据库主从同步,再通过读写分离可以分担数据库压力,提高性能。

3.4 主从复制与读写分离

在实际的生产环境中,对数据库的读和写都在同一个数据库服务器中,是不能满足实际需求的。无论是在安全性、高可用性还是高并发等各个方面都是完全不能满足实际需求的。因此,通过主从复制的方式来同步数据,再通过读写分离来提升数据库的并发负载能力。有点类似于rsync,但是不同的是rsync是对磁盘文件做备份,而mysql主从复制是对数据库中的数据、语句做备份。

3.5 MySQL 读写分离原理

读写分离就是只在主服务器上写,只在从服务器上读。基本的原理是让主数据库处理事务性操作,而从数据库处理 select 查询。数据库复制被用来把主数据库上事务性操作导致的变更同步到集群中的从数据库。

3.6 较为常见的 MySQL 读写分离方法

  1. 基于程序代码内部实现
    在代码中根据 select、insert 进行路由分类,这类方法也是目前生产环境应用最广泛的。
    优点是性能较好,因为在程序代码中实现,不需要增加额外的设备为硬件开支;缺点是需要开发人员来实现,运维人员无从下手。
    但是并不是所有的应用都适合在程序代码中实现读写分离,像一些大型复杂的Java应用,如果在程序代码中实现读写分离对代码改动就较大。

  2. 基于中间代理层实现
    代理一般位于客户端和服务器之间,代理服务器接到客户端请求后通过判断后转发到后端数据库,有以下代表性程序。
    (1)MySQL-Proxy。MySQL-Proxy 为 MySQL 开源项目,通过其自带的 lua 脚本进行SQL 判断。
    (2)Atlas。是由奇虎360的Web平台部基础架构团队开发维护的一个基于MySQL协议的数据中间层项目。它是在mysql-proxy 0.8.2版本的基础上,对其进行了优化,增加了一些新的功能特性。360内部使用Atlas运行的mysql业务,每天承载的读写请求数达几十亿条。支持事物以及存储过程。
    (3)Amoeba。由陈思儒开发,作者曾就职于阿里巴巴。该程序由Java语言进行开发,阿里巴巴将其用于生产环境。但是它不支持事务和存储过程。
    (4)Mycat。是一款流行的基于Java语言编写的数据库中间件,是一个实现了MySql协议的服务器,其核心功能是分库分表。配合数据库的主从模式还可以实现读写分离。

由于使用MySQL Proxy 需要写大量的Lua脚本,这些Lua并不是现成的,而是需要自己去写。这对于并不熟悉MySQL Proxy 内置变量和MySQL Protocol 的人来说是非常困难的。
Amoeba是一个非常容易使用、可移植性非常强的软件。因此它在生产环境中被广泛应用于数据库的代理层。

四、读写分离实验

4.1 架构图

在这里插入图片描述
环境准备(基于之前的主从复制):

  1. 客户端:IP为192.168.41.72,centos7系统,安装mysql
  2. Amoeba服务器:IP为192.168.41.46,centos7系统,安装Amoeba软件和jdk1.6版本

4.2 实验步骤

--------------------amoeba服务器--------------------
----------------------jdk1.6的安装-------------------
因为amoeba是基于jdk1.5开发的,所以推荐使用jdk1.5或1.6,高版本不建议使用
1.上传jdk1.6的bin包和amoeba安装包至opt目录
2.安装jdk1.6
cd /opt
cp jdk-6u14-linux-x64.bin /usr/local/
cd /usr/local/
chmod +x jdk-6u14-linux-x64.bin     #为二进制文件增加执行权限
./jdk-6u14-linux-x64.bin
按q到最下方,回车输入yes,再回车
mv jdk1.6.0_14/ /usr/local/jdk1.6   #重命名jdk1.6文件夹
3.添加环境变量
vim /etc/profile     #编辑profile文件
export JAVA_HOME=/usr/local/jdk1.6
export CLASSPATH=.:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
export AMOEBA_HOME=/usr/local/amoeba
export PATH=$PATH:$AMOEBA_HOME/bin
source /etc/profile   #加载文件
java -version         #查看Jdk版本
----------------------------------------------------------------

-----------------------amoeba安装----------------------------
mkdir /usr/local/amoeba/   #创建amoeba的解压目录
cd /opt
tar zxvf amoeba-mysql-binary-2.2.0.tar.gz -C /usr/local/amoeba/   #将amoeba指定解压目录为amoeba
chmod -R 755 /usr/local/amoeba/   #赋予amoeba目录755权限
/usr/local/amoeba/bin/amoeba   #如显示amoeba start|stop说明安装成功

---------------------各数据库操作--------------------
grant all on *.* to hang@'192.168.41.%' identified by '123456';   #各数据库开放权限用于amoeba访问

----------------------amoeba配置文件-----------------------
cd /usr/local/amoeba/conf/   
cp amoeba.xml amoeba.xml.bak   #将amoeba配置文件备份
vim amoeba.xml   #因为amoeba是由java编写,所以是xml结构的文件
<property name="user">amoeba</property>   #第30行左右修改为amoeba,该配置用于授权客户端用于登录amoeba的账号
<property name="password">123456</property>   #第32行修改密码为123456,该配置用于授权客户端用于登录amoeba的密码
<property name="defaultPool">master</property>  #第115行,设置默认服务器池,为master

<property name="writePool">master</property>   #117行去掉注释,定义写的服务器池名称,为master
<property name="readPool">slaves</property>    #定义读的服务器池名称,读在slave上,所以参数为slaves

cp dbServers.xml dbServers.xml.bak   #备份数据库文件
vim /dbServers.xml   #修改
<!-- <property name="schema">test</property> -->   #将23行注释,防止数据库中没有test库而报错
<property name="user">hang</property>    #26行,改为我们在各数据库授权的用户hang
<property name="password">123456</property>   #28行,注释去掉,password参数改为123456,我们数据库授权的密码
<dbServer name="master"  parent="abstractServer">   #45行,设置主服务器的名称,为master
<property name="ipAddress">192.168.41.47</property>   #48行,设置master的ip
<dbServer name="slave1"  parent="abstractServer">   #52行,设置从服务器1的名称slave1
<property name="ipAddress">192.168.41.48</property>   #slave1的ip
<dbServer name="slave2"  parent="abstractServer">   #设置从服务器2的名称slave2,由于配置文件中没有,但slave1的模板我们可以复制过来修改即可
<property name="ipAddress">192.168.41.71</property>   #slave2的ip
<dbServer name="slaves" virtual="true">   #65行设置为slaves,定义slave群
<property name="poolNames">slave1,slave2</property>   #定义slave群中的name,是slave1和slave2,对应上面的slave1和slave2,如果是别的,对应即可
/usr/local/amoeba/bin/amoeba start &   #后台启动amoeba,但是会在前台进行运行,我们按ctrl+c返回即可
netstat -anpt | grep java   #查看java相关的的端口信息

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

---------------------客户端--------------------------------
mysql -u amoeba -p123456 -h 192.168.41.46 -P8066
#mysql登录amoeba服务器,所以用的是amoeba账号和123456的密码,-h指定的是amoeba服务器地址,-P是amoeba的8066端口

----------------------测试------------------------
客户端服务器写操作:
create database myhome;
use myhome;
create table me(name char(10),age int);
insert into me values('zhangsan',18),('lisi',20);
mysql> select * from me;

master、slaves都查看:
登录mysql
show databases;
show tables from myhome;
select * from myhome.me;

读写分离测试:
slave1服务器执行:
stop slave;   #slave服务器关闭同步
use myhome;
insert into me values('wangwu',12);
slave2服务器执行:
stop slave;   #slave服务器关闭同步
use myhome;
insert into me values('zhaoliu',46);
amoeba服务器查询:
select * from myhome.me;

master服务器添加一条数据后,slave服务器start slave
amoeba服务器再次查询:select * from myhome.me;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结

  1. 主从复制的原理
  2. 什么情况会使得半同步复制降为异步复制?以及什么时候又会恢复同步复制?
  3. 主从不一致的解决方法
  4. 主从复制延迟原因和优化方法

猜你喜欢

转载自blog.csdn.net/qq_40707090/article/details/125228232