您的位置 首页 java

MySQL 主从复制、读写分离理论分析+实战演示

引言

在企业应用中,成熟的业务通常数据量都比较庞大,如果对 MySQL 数据库 的读和写都在一台 数据库服务器 上操作,无论是在安全性、高可用性,还是高并发等各个方面都是不能满足实际需求的。因此,一般来说都是通过主从复制(Master-Slave)的方式来同步数据,再通过 读写分离 来提升数据库的并发负载能力这样的方案进行部署与实施。

一、主从复制

1. 原理

MySQL 的主从复制和 MySQL 的读写分离两者有着紧密联系,首先要部署主从复制,只有主从复制完成了,才能在此基础上进行数据的读写分离。

  • 主从复制的核心:2个日志、3个 线程

master 线程:bin log 二进制 日志、dump 线程

master 上的 dump 线程会监控 bin log 二进制日志的更新,若有更新会通知 slave的 I/O 线程

slave:relay log 中继日志、I/O 线程、SQL 线程

线程1:

slave上的 I/O 线程会向 master 申请同步 bin log 二进制日志的更新内容,slave的 I/O 线程把更新内容写入自己的中继日志

线程2:

slave 的 SQL 线程把日志中的更新语句同步执行到内容,以达到和 master 数据库趋于一致

2. 支持的复制类型

STATEMENT :基于语句的复制,在主服务器上执行的 SQL 语句 ,在从服务器上执行同样的语句;MySQL 默认采用基于语句的复制,效率比较高

ROW:基于行的复制,把改变的内容复制过去,而不是把命令在从服务器上执行一遍

MIXED:混合类型的复制,默认采用基于语句的复制,一旦发现基于语句无法精确复制时,就会采用基于行的复制

3. 复制的工作过程

① 在每个事务更新数据完成之前,Master 都会在二进制日志记录这些改变;写入二进制日志完成后,Master 通知存储引擎提交事务;

② Slave 将 Master 的 Binary log 复制到其中继日志

首先,Slave 开始一个工作线程——I/O 线程,I/O 线程在 Master 上打开一个普通的连接,然后开始 Binlog dump process;Binlog dump process 从 Master 的二进制日志中读取时间,如果已经跟上 Master,它会睡眠并等待Master 产生新的时间;I/O 线程将做这些事件写入中继日志;

③ SQL slave thread(SQL 从线程)处理该过程的最后一步

SQL 线程从中继日志读取事件,并重放其中的事件而更新 Slave 的数据,使其与 Master 中的数据一致;只要该线程与 I/O 线程保持一致,中继日志通常会位于 OS 的缓存中,所以中继日志的开销很小;

④ 复制过程有一个很重要的限制,即复制在 Slave 上是串行化的,也就是说 Master 上的并行更新操作不能在 Slave 上并行操作。

4. MySQL主从复制延迟

(1)master 服务器高并发,形成大量事务

(2)网络延迟

(3)主从硬件设备导致:cpu主频、内存 IO 、硬盘io

(4)本来就不是同步复制、而是异步复制

(5)从库优化 Mysql 参数,比如增大 innodb _buffer_pool_size,让更多操作在 Mysql 内存中完成,减少磁盘操作。

(6)从库使用高性能主机,包括 cpu 强悍、内存加大。避免使用虚拟云主机,使用物理主机,这样提升了I/O方面性能。

(7)从库使用 SSD 磁盘

(8)网络优化,避免跨机房实现同步

二、读写分离

1. 原理

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

为什么要读写分离呢?

因为数据库写 10000 条数据可能要 3 分钟,操作比较耗时,但是数据库读 10000 条数据可能只要5秒钟,所以读写分离,解决的是,数据库的写入,影响了查询的效率。

读写分离的过程如下图所示

2. 读写分离作用

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

3. 实现方式

  • 读写分离分为以下两种∶

3.1 基于程序代码内部实现

在代码中根据 select、insert,进行路由分类,这类方法也是目前生产环境应用最广泛的。

优点是性能较好,因为在程序代码中实现,不需要增加额外的设备为硬件开支; 缺点是需要开发人员来实现,运维人员无从下手。

但是并不是所有的应用都适合在程序代码中实现读写分离,像一些大型复杂的 Java 应用,如果在程序代码中实现读写分离对代码改动就较大。

3.2 基于中间代理层实现

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

(1) mysql-proxy :MySQL-Proxy 为 MySOL开源项目, 通过其自带的 Lua 脚本进行SOL 判断。

(2) Atlas :是由 奇虎360 的Web平台部基础架构团队开发维护的一个基于MySQL协议的数据中间层项目。它是在mysql-proxy0.8.2版本的基础上,对其进行了优化, 增加了一些新的功能特性。 360 内部使用Atlas运行的mysql业务,每天承载的读写请求数达几十亿条。支持事物以 及存储过程。

(3) Amoeba :由 陈思儒 开发,作者曾就职于 阿里巴巴 。该程序由Jaya语言进行开发,阿里巴巴将其用于生产环境。但是它不支持事务和存储过程。

由于使用 MySQL Proxy 需要写大量的 Lua 脚本,这些 Lua 并不是现成的,而是需要自己去写。这对于并不熟悉 MySQL Proxy 内置变量和 MySQL Protocol 的人来说是非常困难的。

Amoeba是一个非常容易使用、可移植性非常强的软件。因此它在生产环境中被广泛应用于数据库的代理层。

三、MySQL 主从复制架构搭建

1. 服务器配置

主机名

操作系统

IP地址

所需软件

Master

CentOS 7

192.168.10.14

mysql-5.7

Amoeba

CentOS 7

192.168.10.13

jdk 1.6、Amoeba

Slave1

CentOS 7

192.168.10.16

mysql-5.7

Slave2

CentOS 7

192.168.10.19

mysql-5.7

client端

CentOS 7

192.168.10.26

mysql5.7

修改主机名

2. 环境准备

#关闭防火墙与安全增强系统

systemctl stop firewalld && systemctl disable firewalld

setenforce 0

setenforce: SELinux is disabled

3. mysql 主从服务器时间同步

主服务器:192.168.10.14

 [root@localhost ~]#yum install -y ntp
[root@localhost ~]#vim /etc/ntp.conf #尾行加入下面内容

server 127.127.10.0#设置本地是时钟源,注意修改自己的网段
fudge 127.127.10.0 stratum 8#设置时间层级为 8(限制在 15 内)
[root@master ~]#systemctl start ntpd  

slave1 :192.168.10.16

 [root@ky19 ~]#yum install -y ntp ntpdate
[root@ky19 ~]#systemctl start ntpd 
[root@ky19 ~]# /usr/sbin/ntpdate 192.168.10.14              #时间同步
27 Jun 20:45:40 ntpdate[20801]: the NTP  socket  is in use, exiting

[root@ky19 ~]#crontab -e#设置计划任务

*/30 * * * * /usr/sbin/ntpdate 192.168.10.14  

slave2 :192.168.10.26

4. 主服务器的 mysql 配置

[root@localhost ~]#vim /etc/my.cnf

#添加下面配置

server-id = 1 #定义 server-id,每台主机不可相同

log-bin=master-bin #主服务器开启二进制日志

binlog_format = MIXED #本次使用 MIXED 模式

log-slave-updates=true #允许从服务器更新二进制日志

#注意:default-character-set=utf8 这个需要注释掉,不然会报错

systemctl restart mysqld.service #重启服务

#设置从服务器账号并授权

mysql -uroot -p123456

#给从服务器授权

mysql> GRANT REPLICATION SLAVE ON *.* TO ‘myslave’@’192.168.10.%’ IDENTIFIED BY ‘123456’;

mysql> flush privileges;

mysql> use mysql;

mysql> show master status;

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

5. 从服务器的 mysql 配置

5.1 slave1 配置(192.168.10.16)

vim /etc/my.cnf

server-id = 2 # 注意 id 与其他主机都不能相同

relay-log=relay-log-bin #开启中继日志,从主服务器上同步日志文件记录到本地

relay-log-index=slave-relay-bin.index #定义中继日志文件的位置和名称

relay_log_recovery = 1 #选配项

#当 slave 从库宕机后,假如 relay -log 损坏了,导致一部分中继日志没有处理,则自动放弃所有未执行的 relay-log,

并且重新从 master 上获取日志,这样就保证了relay-log 的完整性。

默认情况下该功能是关闭的,将 relay_log_recovery 的值设置为 1 时, 可在 slave 从库上开启该功能,建议开启。

systemctl restart mysqld

mysql -uroot -p123456

#配置同步,注意 master_log_file 和 master_log_pos 的值要与 Master 查询的一致

mysql> CHANGE master to master_host=’192.168.10.14′,master_user=’myslave’,master_password=’123456′,master_log_file=’master-bin.000002′,master_log_pos=603;

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

mysql> show slave status\G; #查看 Slave 状态,确保 IO 和 SQL 线程都是 Yes,代表同步正常

5.2 slave2 配置(192.168.10.26)

vim /etc/my.cnf

server-id = 3 #注意不能和 master 、slave1 相同

relay-log=relay-log-bin

relay-log-index=slave-relay-bin.index

relay_log_recovery = 1

systemctl restart mysqld

mysql -uroot -p123456

mysql> CHANGE master to master_host=’192.168.10.14′,master_user=’myslave’,master_password=’123456′,master_log_file=’master-bin.000002′,master_log_pos=603;

mysql> start slave;

mysql> show slave status\G;

5.3 一般 Slave_IO_Running: No 的可能性

  • 网络不通
  • my.cnf 配置有问题
  • 密码、file 文件名、pos 偏移量不对
  • 防火墙没有关闭

6. 验证主从复制

#主服务器

mysql> show databases;

mysql> create database test;

mysql> show databases;

#slave1(192.168.10.16)

mysql> show databases;

#slave2(192.168.10.26)

mysql> show databases;

四、MySQL 读写分离架构搭建

1. amoeba 服务器安装配置

安装Java 环境

因为 amoeba 是基于 jdk1.5 开发的,所以官方推荐使用 jdk1.5 或 1.6 版本,高版本不建议使用。

[ root @localhost ~]#cd /opt/

[root@localhost /opt]#ls

amoeba-mysql-binary-2.2.0.tar.gz jdk-6u14-linux-x64.bin rh

cp jdk-6u14-linux-x64.bin /usr/local/

cd /usr/local/

chmod +x jdk-6u14-linux-x64.bin

./jdk-6u14-linux-x64.bin

#按住Enter,提示输入YES等待完成并回车

mv jdk1.6.0_14/ /usr/local/jdk1.6

#编辑全局配置文件,在最后一行添加以下配置

vim /etc/profile

export JAVA_HOME=/usr/local/jdk1.6

export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$ java _HOME/jre/lib

export PATH=$JAVA_HOME/lib:$JAVA_HOME/jre/bin/:$PATH:$HOME/bin

export AMOEBA_HOME=/usr/local/amoeba

export PATH=$PATH:$AMOEBA_HOME/bin

#输出定义Java的工作目录

#输出指定的java类型

#将java加入路径环境变量

#输出定义amoeba工作目录

#加入路径环境变量

[root@localhost /usr/local]#source /etc/profile #执行修改后的全局配置文件

[root@localhost /usr/local]#java -version #查看java版本信息以检查是否安装成功

java version “1.6.0_14”

Java(TM) SE Runtime Environment (build 1.6.0_14-b08)

Java HotSpot(TM) 64-Bit Server VM (build 14.0-b16, mixed mode)

安装 Amoeba

[root@localhost /usr/local]#mkdir /usr/local/amoeba

[root@localhost /usr/local]#tar zxvf /opt/amoeba-mysql-binary-2.2.0.tar.gz -C /usr/local/amoeba/

[root@localhost /usr/local]#chmod -R 755 /usr/local/amoeba/ #为目录赋予权限

[root@localhost /usr/local]#/usr/local/amoeba/bin/amoeba #开启amoeba

amoeba start|stop #提示stop说明安装成功

2. 配置 amoeba 读写分离,两个 slave 读负载均衡

在master、slave1、slave2 的 mysql上开放权限给 amoeba 访问

 mysql> grant all on *.* to test@'192.168.10.%' identified by '123456';
mysql> select user,host,authentication_string from mysql.user;  

amoeba 服务器配置 amoeba 服务

 #先备份
cd /usr/local/amoeba/conf/
cp amoeba.xml amoeba.xml.bak
cp dbServers.xml dbServers.xml.bak
vim /usr/local/amoeba/conf/amoeba.xml#修改 amoeba 配置文件
#--30行--
<property name="user">amoeba</property>
#--32行-- 
<property name="password">123456</property>
#--115行--
<property name="defaultPool">master</property>
#--117行-120行,取消注释并修改
<property name="writePool">master</property>
<property name="readPool">slaves</property>
......
-------------------------------------------------------------------------------------------
vim /usr/local/amoeba/conf/dbServers.xml#修改数据库配置文件
#--23行--注释掉  作用:默认进入 test 库,以防 mysql 中没有 test 库时会报错
<!-- <property name="schema">test</property> -->
#--26--修改,使用之前创建的授权用户
<property name="user">test</property>
#--29--去掉注释,密码为之前创建的授权用户密码
<property name="password">123456</property>
#--45--修改,设置主服务器的名 master
<dbServer name="master"  parent="abstractServer">
#--48--修改,设置主服务器的地址
<property name="ipAddress">192.168.10.20</property>
#--52--修改,设置从服务器的名 slave1
<dbServer name="slave1"  parent="abstractServer">
#--55--修改,设置从服务器1的地址
<property name="ipAddress">192.168.10.30</property>
#--58--复制上面 6 行粘贴,设置从服务器 2 的名 slave2 和地址
<dbServer name="slave2"  parent="abstractServer">
<property name="ipAddress">192.168.10.40</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
tcp6       0      0  127.0.0.1 :61721         :::*                    LISTEN      4346/java           
tcp6       0      0 :::8066                 :::*                    LISTEN      4346/java           
tcp6       0      0 192.168.8.17:58290      192.168.8.15:3306       ESTABLISHED 4346/java           
tcp6       0      0 192.168.8.17:58300      192.168.8.14:3306       ESTABLISHED 4346/java           
tcp6       0      0 192.168.8.17:56808      192.168.8.16:3306       ESTABLISHED 4346/java  

# 先备份

cd /usr/local/amoeba/conf/

cp amoeba.xml amoeba.xml.bak

cp dbServers.xml dbServers.xml.bak

vim /usr/local/amoeba/conf/amoeba.xml #修改 amoeba 配置文件

#–30行–

<property name=”user”>amoeba</property>

#–32行–

<property name=”password”>123456</property>

#–115行–

<property name=”defaultPool”>master</property>

#–117行-120行,取消注释并修改

<property name=”writePool”>master</property>

<property name=”readPool”>slaves</property>

vim /usr/local/amoeba/conf/dbServers.xml #修改数据库配置文件

#–23行–注释掉 作用:默认进入 test 库,以防 mysql 中没有 test 库时会报错

<!– <property name=”schema”>test</property> –>

#–26–修改,使用之前创建的授权用户

<property name=”user”>test</property>

#–29–去掉注释,密码为之前创建的授权用户密码

<property name=”password”>123456</property>

#–45–修改,设置主服务器的名 master

<dbServer name=”master” parent=”abstractServer”>

#–48–修改,设置主服务器的地址

<property name=”ipAddress”>192.168.10.14</property>

#–52–修改,设置从服务器的名 slave1

<dbServer name=”slave1″ parent=”abstractServer”>

#–55–修改,设置从服务器1的地址

<property name=”ipAddress”>192.168.10.16</property>

#–58–复制上面 6 行粘贴,设置从服务器 2 的名 slave2 和地址

<dbServer name=”slave2″ parent=”abstractServer”>

<property name=”ipAddress”>192.168.10.26</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

3. 客户端测试读写分离

client:192.168.10.26 ,已装好 MySQL 服务

客户端(10.26)

 [root@client ~]#mysql -uamoeba -p123456 -h 192.168.10.13 -P8066
mysql> show databases;

mysql> use test;
mysql> create table test(id int,name char(20));
mysql> show tables;  

master 服务器

 mysql> use test;

mysql> show tables;  

slave 1 服务器

 mysql> show databases;

mysql> use test;
mysql> show tables;  

slave 2 服务器

 mysql> show databases;

mysql> use test;
mysql> show tables;  

可以看到客户端中做出的修改,在所有服务器上都能看到。

4. 关闭 slave 功能后在主从服务器插入数据

slave 1 服务器

 mysql> stop slave;
mysql> insert into test values(1,'slave1');
mysql> select * from test;
  

slave 2 服务器

 mysql> stop slave;
mysql> insert into test values(2,'slave2');
mysql> select * from test;
  

master 服务器

 mysql> show tables;
mysql> insert into test values(3,'master');
mysql> select * from test;
  

client 服务器

mysql -uamoeba -p123456 -h 192.168.10.13 -P8066

通过amoeba服务器代理访问mysql,在客户端连接mysql后写入的数据只有主服务会记录,然后同步给从–从服务器

 use test;
mysql> select * from test;
mysql> select * from test;
mysql> select * from test;
mysql> select * from test;
  

上面输出结果中:客户端在 slave1、slave2 中轮询查看数据。由于从服务器都关闭了 slave 功能,因此无法查看 master 服务器中的修改。

5. 关闭 slave 功能后,客户端服务器写入数据

客户端服务器

 mysql> insert into test values(4,'client1');
mysql> select * from test;
mysql> select * from test;
  

master 服务器

 mysql> select * from test;
  

slave 1 服务器

 mysql> select * from test;
  

slave 2 服务器

 mysql> select * from test;
  

结果:

客户端服务器的修改操作是对于 master 服务器进行的,因此 master 服务器上可以看到客户端服务器所做的 insert 操作。由于客户端服务器的 select 操作是对从服务器进行的,而从服务器关闭了 slave 功能,无法获取更新,因此客户端服务器本身以及从服务器都无法查看更新操作。

6. 从服务器开启 slave 服务

slave 1 服务器

 mysql> start slave;
mysql> select * from test;
  

slave 2 服务器

 mysql> start slave;
mysql> select * from test;
  

master 服务器

 mysql> select * from test;
  

客户端服务器

 mysql> select * from test;
mysql> select * from test;
  

结果:

从服务器开启 slave 服务后可以从 master 服务器上获取更新,但是从服务器上的更新不会被 master 服务器看到。

7. 客户端服务器再次插入数据

客户端服务器

 mysql> insert into test values(5,'client2');
mysql> select * from test;
  

master 服务器

 mysql> select * from test;
  

slave1 服务器

 mysql> select * from test;
  

slave2 服务器

 mysql> select * from test;
  

结果:

客户端服务器上的数据修改,会同步到所有服务器中。

总结

1、MySQL 主从复制原理:

(1)MySQL主服务器在用户增删改查操作,提交事务的以后,会按顺序写入到binlog日志中,从服务器节点会在一定时间间隔内对主服务器的二进制日志进行探测,观察是否发生数据改变,若发生改变则开启一个I/O线程请求主服务器的二进制事件。

(2)同时主服务器会为每个I/O线程开启dump线程,用于向其发送二进制事件,并保存到从服务器的中继日志中,从服务器会开启SQL线程从中继日志中读取二进制日志,在本地重放,也就是说把二进制日志解析成SQL语句逐一执行,使其数据与主服务器保持一致,最后I/O线程和SQL线程会进入休眠状态,等待下一次被唤醒。

2、当 MySQL 主从复制在 show slave status\G 时出现 Slave_IO_Running 或 Slave_SQL_Running 的值不为YES时,需要首先通过 stop slave 来停止从服务器,然后再执行一次上一步操作即可恢复。

3、但如果想尽可能的同步更多的数据,可以在 Slave 上将 master_log_pos 节点的值在之前同步失效的值的基础上增大一些,然后反复测试,直到同步OK。

4、因为MySQL主从复制的原理其实就是从服务器读取主服务器的 binlog,然后根据 binlog 的记录来更新数据库。

文章来源:智云一二三科技

文章标题:MySQL 主从复制、读写分离理论分析+实战演示

文章地址:https://www.zhihuclub.com/177392.shtml

关于作者: 智云科技

热门文章

网站地图