MySQL主从复制与读写分离

198 阅读18分钟

1. 概念

1.1 什么是读写分离?

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

1.2 为什么要读写分离呢?

因为数据库的“写”(写10000条数据可能要3分钟)操作是比较耗时的。

但是数据库的“读”(读10000条数据可能只要5秒钟);

所以读写分离,解决的是,数据库的写入,影响了查询的效率。

1.3 什么时候要读写分离?

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

1.4 主从复制与读写分离

在实际的生产环境中,对数据库的读和写都在同一个数据库服务器中,是不能满足实际需求的。无论是在安全性、高可用性还是高并发等各个方面都是完全不能满足实际需求的。

因此,通过主从复制的方式来同步数据,再通过读写分离来提升数据库的并发负载能力。有点类似于rsync(文件同步工具),但是不同的是rsync是对磁盘文件做备份,而mysql主从复制是对数据库中的数据、语句做备份。

1.5 mysql支持的复制类型

(1) STATEMENT:基于语句的复制。在服务器上执行sql语句,在从服务器上执行同样的语句,mysql默认采用基于语句的复制(5.7版本之前),执行效率高。高并发的情况可能会出现执行顺序的误差,事务的死锁。

(2)ROW:基于行的复制。把改变的内容复制过去,而不是把命令在从服务器上执行一 遍。精确,但效率低,保存的文件会更大。(5.7版本之后默认采用ROW模式)

(3)MIXED:混合类型的复制。默认采用基于语句的复制,一旦发现基于语句无法精确复制时,就会采用基于行的复制。更智能,所以大部分情况下使用MIXED。

1.6 主从复制的工作过程

Master节点需要开启二进制日志,Slave节点需要开启中继日志。

(1)Master 节点将数据的改变记录成二进制日志(bin log) ,当Master上的数据发生改变时(增删改),则将其改变写入二进制日志中。

(2)Slave节点会在一定时间间隔内对Master的二进制日志进行探测其是否发生改变,如果发生改变,则开始一个I/O线程请求Master的二进制事件。(请求二进制数据)

(3)同时Master 节点为每个I/O线程启动一个dump线程,用于通知和向其发送二进制事件,I/O线程接收到bin-log内容后,将内容保存至slave节点本地的中继日志(Relay log)中,Slave节点将启动SQL线程从中继日志中读取二进制事件,在本地重放,即解析成sql 语句逐一执行,使得其数据和Master节点的保持一致。最后I/O线程和SQL线程将进入睡眠状态,等待下一次被唤醒。

image.png

注:

  • 中继日志通常会位于 OS 缓存中,所以中继日志的开销很小。
  • 复制过程有一个很重要的限制,即复制在 Slave上是串行化的,也就是说 Master上的并行更新操作不能在 Slave上并行操作。
  • 半同步复制,会多一个ack确认线程(ack collector thread),专门用于接收slave 的反馈信息(收集slave节点返回的ack信息)。

1.7 mysql 读写分离原理

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

1.8 目前较为常见的 MySQL读写分离分为一下两种:

1)基于程序代码内部实现

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

2)基于中间代理层实现

代理一般位于客户端和服务器之间,代理服务器接到客户端请求后通过判断后转发到后端数据库,有以下代表性程序。

  1. MySQL-Proxy。MySQI-Proxy为 MysQL开源项目,通过其自带的 lua 脚本进行SQL 判断。
  2. Atlas。是由奇虎360的web平台部基础架构团队开发维护的一个基于wysQL协议的数据中间层项目。它是在mysql-proxy 0.8.2版本的基础上,对其进行了优化,增加了一些新的功能特性。360内部使用Atlas运行的mysql业务,每天承载的读写请求数达几十亿条。支持事物以及存储过程。
  3. Amoeba。由陈思儒开发,作者曾就职于阿里巴巴。该程序由Java语言进行开发,阿里巴巴将其用于生产环境。但是它不支持事务和存储过程。
  4. Mycat。是一款流行的基于Java语言编写的数据库中间件,是一个实现了mysql协议的服务器,其核心功能是分库分表。配合数据库的主从模式还可以实现读写分离。
  • 由于使用MysqI Proxy需要写大量的Lua脚本,这些Lua并不是现成的,而是需要自己去写。
  • 这对于并不熟悉MysQL Proxy 内置变量和MysQL Protocol 的人来说是非常困难的。
  • Amoeba是一个非常容易使用、可移植性非常强的软件。因此它在生产环境中被广泛应用于数据库的代理层。

2. 主从复制案例演示(异步复制)

每台机器都必须关闭防火墙和selinux

systemctl stop firewalld
systemctl disable firewalld
setenforce 0

2.1 MYSQL主从服务器时间同步

2.2 主服务器使用本地时钟源。

#系统默认已经安装了这两个软件
rpm -q ntp ntpdate

#如果么有安装就使用yum自行安装即可 
yum -y install ntp ntpdatr

#修改ntp配置文件,在末尾加入
vim /etc/ntp.conf
server 127.127.72.0              #设置本地时钟源,注意修改网段(72是网段) fudge 127.127.72.0 stratum 8     #设置时间层级为8(限制在15以内)

#开启ntpd(两种方式)
systemctl start ntpd
service ntpd start

QQ截图20221031213623.png

2.3 可以与在线源进行对接

ntpdate ntp.aliyun.com

QQ截图20221031214819.png

2.4 两台从服务器同步主服务器的时间。

#与本地源做对接
ntpdate 192.168.85.10 

#开启ntpd
systemctl start ntpd

#设置定时任务,每30分钟同步一次  
crontab -e    
*/30 * * * * /usr/sbin/ntpdate 192.168.85.10

2.5 主服务器的mysql配置

vim /etc/my.cnf  
[mysqld]  
server-id = 1               #指定服务ID号,master和两台slave都要不同
log-bin=mysql-bin #添加,主服务器开启二进制日志  
binlog_format = MIXED       #指定二进制日志(binlog)的记录格式为MIXED
binlog_format = MIXED       #指定二进制日志(binlog)的记录格式为MIXED
expire_logs_days = 7 #设置二进制日志文件过期时间,默认值为0,表示logs不过期
max_binlog_size = 500M #设置二进制日志限制大小,如果超出给定值,日志就会发生滚动,默认值是1GB

#重启服务
systemctl restart mysqld.service

#登录数据库
mysql -uroot -pabc123
#给从服务器授权
grant replication slave on *.* to 'myslave'@'192.168.85.%' identified by '123123';

flush privileges;               #刷新权限

show master status;     #查看主服务器状态
+------------------+----------+--------------+------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000007 |      603 |              |                  |                   |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

#File 列显示日志名,Position 列显示偏移量

QQ截图20221031221335.png

QQ截图20221031222541.png

2.6 从服务器的mysql配置

#修改配置文件
vim /etc/my.cnf
[mysqld]
server-id = 2     #修改,注意id与Master的不同,两个Slave的id也要不同
relay-log=relay-log-bin #添加,开启中继日志,从主服务器上同步日志文件记录到本地
relay-log-index=slave-relay-bin.index #添加,定义中继日志索引文件的位置和名称,一般和relay-log在同一目录  r
elay_log_recovery = 1                  #选配项
#当 slave 从库宕机后,假如 relay-log 损坏了,导致一部分中继日志没有处理,则自动放弃所有未执行的 relay-log,并且重新从 master 上获取日志,这样就保证了 relay-log 的完整性。默认情况下该功能是关闭的,将 relay_log_recovery 的值设置为 1 时, 可在 slave 从库上开启该功能,建议开启。

#重启服务  
systemctl restart mysqld
 
#登录数据库,进行同步设置 
change master to master_host='192.168.85.10',master_user='myslave',master_password='123123',master_log_file='mysql-bin.000007',master_log_pos=603;
#配置同步,注意 master_log_file 和 master_log_pos 的值要与Master查询的一致

start slave;     #启动同步,如有报错执行 reset slave;

show slave status\G    #查看 Slave 状态
##确保 IO 和 SQL 线程都是 Yes,代表同步正常。  
Slave_IO_Running: Yes #负责与主机的IO通信  
Slave_SQL_Running: Yes #负责自己的slave mysql进程

  
##一般 "Slave_IO_Running: No" 的可能原因: 
1. 网络不通  
2. my.cnf配置有问题(server-id重复)  
3. 密码、file文件名、pos偏移量不对  
4. 防火墙没有关闭

QQ截图20221031223204.png

QQ截图20221101000254.png

Slave2 服务器也是同样配置,注意配置文件中 server-id 要和前面两台不同,我这里设置为 "server-id =3"。

2.7 验证主从复制效果

主服务器上创建新的数据库,执行 create database ww1;

去从服务器上查看是否同步成功,执行 show databases;

QQ截图20221101001011.png

QQ截图20221101001037 - 副本.png

3. 主从复制案例演示(半同步复制)

3.1 实验操作:

 -------1、所有服务器关闭防火墙------
 systemctl disable --now firewalld
 setenforce 0
 ​
 ​
 -------2、主数据库配置---------
 vim /etc/my.cnf             #在 [mysqld] 区域添加下面内容
 ......
 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
 ​
 ​
 -------3、从数据库配置-----------
 vim /etc/my.cnf 
 ......
 plugin-load=rpl_semi_sync_slave=semisync_slave.so
 rpl_semi_sync_slave_enabled=ON
 ​
 systemctl restart mysqld
 ​
 ​
 -------4、查看半同步是否在运行---------------
 #主数据库执行
 show status like 'Rpl_semi_sync_master_status';
 show variables like 'rpl_semi_sync_master_timeout';
 ​
 #从数据库执行(此时可能还是OFF状态,需要在下一步重启IO线程后,从库半同步状态才会为ON)
 show status like 'Rpl_semi_sync_slave_status';
 ​
 #重启从数据库上的IO线程
 STOP SLAVE IO_THREAD;
 START 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事物回复的次数
 ​
 当半同步复制发生超时(由rpl_semi_sync_master_timeout参数控制,默认为10000ms,即10s),会暂时关闭半同步复制,转而使用异步复制,也就是会自动降为异步工作。
 当 master dump 线程发送完一个事务的所有事件之后,如果在 rpl_semi_sync_master_timeout 内,收到了从库的响应, 则主从又重新恢复为半同步复制。

1)在一主多从的架构中,如果要开启半同步复制,并不要求所有的从都是半同步复制。

2)MySQL 5.7极大的提升了半同步复制的性能。

  • 5.6 版本的半同步复制,dump thread 承担了两份不同且又十分频繁的任务:传送binlog 给 slave ,还需要等待 slave 反馈信息,而且这两个任务是串行的,dump thread 必须等待 slave 返回之后才会传送下一个 events 事务。dump thread 已然成为整个半同步提高性能的瓶颈。在高并发业务场景下,这样的机制会影响数据库整体的系统吞吐量(TPS)。
  • 5.7 版本的半同步复制中,独立出一个 ack collector thread ,专门用于接收 slave 的反馈信息。这样 master 上有两个线程独立工作,可以同时发送 binlog 到 slave ,和接收 slave 的反馈。

半同步复制什么情况下会降为异步复制?什么时候又会恢复同步复制?

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

4. 读写分离案例 (使用Amoeba)

1)安装 Java 环境

#因为 Amoeba 基于是 jdk1.5 开发的,所以官方推荐使用 jdk1.5 或 1.6 版本,高版本不建议使用。  ​  
#先将jdk的二进制文件上传到/opt/目录下,之后复制到/usr/local/目录下
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
#一直按着回车,直到出现提示以后出入yes

#最后在按一次回车等会自动退出即可
mv jdk1.6.0_14/ /usr/local/jdk1.6     #将jdk目录重命名

#添加环境变量
vim /etc/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版本

QQ截图20221101003054.png

QQ截图20221101003729.png

QQ截图20221101003919.png

2)安装 Amoeba软件

1、安装 Amoeba软件
#创建Amoeba的解压目录
mkdir /usr/local/amoeba/

#将Amoeba安装包上传到/opt/目录,解压安装包
cd /opt/
tar zxvf amoeba-mysql-binary-2.2.0.tar.gz -C /usr/local/amoeba/

#增加目录权限
chmod -R 755 /usr/local/amoeba/

#开启Amoeba
/usr/local/amoeba/bin/amoeba     #如显示amoeba start|stop说明安装成功

2、配置 Amoeba读写分离,两个 Slave 读负载均衡
#先在Master、Slave1、Slave2 的mysql上开放权限给 Amoeba 访问。注意:这里授权的用户名和密码,会在下一步写入数据库配置文件。  
 grant all on *.* to wa@'192.168.85.%' identified by '123123';

  
#再回到amoeba服务器配置amoeba服务:
cd /usr/local/amoeba/conf/


#备份配置文件,修改amoeba配置文件
cp amoeba.xml amoeba.xml.bak
vim amoeba.xml
--30行--
<property name="user">amoeba</property>     #30行和32行,授权客户端用于登录amoeba的账号和密码
--32行--
<property name="password">123456</property>

--115行--
<property name="defaultPool">master</property>  #设置默认服务器池
--117-去掉注释--
<property name="writePool">master</property>   #定义写的服务器池名称  
<property name="readPool">slaves</property>    #定义读的服务器池名称

#备份数据库配置文件,之后修改数据库配置文件dbServers.xml
cp dbServers.xml dbServers.xml.bak
vim dbServers.xml
--23行--注释掉 作用:默认进入test库,注释掉以防mysql中没有test库时,会报错
<!-- <property name="schema">test</property> -->

##26-30行,此用户就是之前在3台主从服务器上授权的用户,授权amoeba服务器用来登录mysql数据库的用户和密码。
--26行--修改  
<property name="user">wa</property>    
--28-30行--去掉注释  
<property name="password">123123</property>

--45行--修改,设置主服务器的名称master和地址
<dbServer name="master"  parent="abstractServer">
--48行--修改,设置主服务器的地址
<property name="ipAddress">192.168.85.10</property>

--52行--修改,设置从服务器1的名称slave1
<dbServer name="slave1"  parent="abstractServer">
--55行--修改,设置从服务器1的地址
<property name="ipAddress">192.168.85.20</property>
--58行--复制上面6行粘贴,设置从服务器2的名称slave2和地址
<dbServer name="slave2"  parent="abstractServer">  
<property name="ipAddress">192.168.85.30</property>

--65行--修改
<dbServer name="slaves" virtual="true">
--71行--修改
<property name="poolNames">slave1,slave2</property>

/usr/local/amoeba/bin/amoeba start & #后台启动Amoeba软件,按ctrl+c 返回
netstat -anpt | grep java #查看8066端口是否开启,默认端口为TCP 8066

QQ截图20221101004541.png

QQ截图20221101005422.png

QQ截图20221101010516.png

QQ截图20221101011200.png

QQ截图20221101011615.png

QQ截图20221101011741.png

QQ截图20221105235220.png

4.2 报错处理(仅限本篇章)

修改配置文件以后后台运行软件可以看到已经报错,软件并没有正常运行

所以此时在怎么查看软件,也没有端口号

QQ截图20221105235345.png 1.再次解压软件

QQ截图20221105235544.png 2.权限问题

QQ截图20221105235649.png 3.检查/etc/profile文件

QQ截图20221106000012.png 4.后台运行的问题

QQ截图20221105235901.png

4.3 客户端安装mariadb数据库

yum install -y mariadb-server mariadb   #安装mariadb数据库
systemctl start mariadb.service         #启动mariadb
 ​
 ​
 #客户端通过amoeba服务器登录数据库,之后向库中写入数据:
mysql -u zhangsan -p123456 -h192.168.85.40 -P8066      

QQ截图20221106000350.png

QQ截图20221106000432.png

4.4 测试读写分离

 //在两台slave服务器上,关闭同步:
 stop slave;                 #关闭同步
 use yuji666;
 ​
 //在slave1上写入数据:
 insert into class values('1','zhangsan');
 ​
 //在slave2上写入数据:
 insert into class values('2','lisi');
 ​
 //在master服务器上写入数据:
 insert into class values('3','wangwu');
 ​
 //在客户端上查看数据:
 use aa;
 select * from class;        
 #客户端会分别向slave1和slave2读取数据(轮询),显示的只有在两个从服务器上添加的数据,没有在主服务器上添加的数据。说明读写是分离的,只从slave中读取数据。
 ​
 insert into class values('4','wwa',);    //客户端插入数据,只有主服务器上有此数据
 ​
 //在两个从服务器上执行 start slave; 即可实现同步主服务器中添加的数据
 start slave;             #开启同步
 select * from class;    

slave1关闭同步,之后向class表中插入数据: QQ截图20221106005734.png

QQ截图20221106010218.png

5. 总结

两个日志和三个线程:

两个日志:二进制日志(bin log)  、中继日志(Relay log)

三个线程:I/O线程dump线程SQL线程

5.1 主从复制的工作过程

Master节点需要开启二进制日志,Slave节点需要开启中继日志。

(1)Master 节点将数据的改变记录成二进制日志(bin log) ,当Master上的数据发生改变时(增删改),则将其改变写入二进制日志中。

(2)Slave节点会在一定时间间隔内对Master的二进制日志进行探测其是否发生改变,如果发生改变,则开始一个I/O线程请求Master的二进制事件。(请求二进制数据)

(3)同时Master 节点为每个I/O线程启动一个dump线程,用于向其发送二进制事件,并保存至slave节点本地的中继日志(Relay log)中,,Slave节点将启动SQL线程从中继日志中读取二进制事件,在本地重放,即解析成sql 语句逐一执行,使得其数据和Master节点的保持一致。最后I/O线程和SQL线程将进入睡眠状态,等待下一次被唤醒。

读写分离
  在数据库使用较多时,且查询的操作多,更新数据库的操作少的场景下会使用读写分离
  作用, 可以分担数据库压力,提高性能,避免数据库的写入影响查询的效率
  
读写分离   基于 主从复制 实现的

主从复制  就是对数据库的二进制日志中的数据,语句做备份复制

二进制日志格式:
STATEMENT   基于SQL记录复制。 特点:效率高,但在高并发高负载的情况下可能会出现复制误差(就是无法精确复制)

ROW     基于 行 记录复制。 特点:复制的时候精确度较高,但效率较低,日志文件占用空间较大

MIXED  混合模式。默认时基于SQL记录,在搞并发高负载时无法精确复制时会采用基于行的记录复制





读写分离   在主库写(create insert update delete 等事务性操作),在从库上读(select)
          主库通过主从服务进行同步到从库
          
实现方式
通过代码逻辑实现   性能好,成本低,主要由开发人员维护
通过中间代理应用实现   需要添加额外设备,成本较高,但通用性较好,由运维人员维护
mysql——proxy   amoeba(jdk1.5)   mycat 


主从复制
1.主从服务器做时间同步
2.修改配置文件,server -id bin-log  relay-log
3.grant replication slave   show master status 日志名 日志偏移量
  从  change master to        start slave    show slave status\G   lo  SQL 状态yes
  
主从复制  IO  SQL线程状态不对
主从复制延迟
主从复制不一致


读写分离
1、安装JDK1.5 1.6
2、安装 amoeba
3、主从数据库进行amoeba用户授权
4、修改 amoeba 服务器配置 和 数据库配置
5、启动 amoeba ,使用客户端进程远程连接测试