主从复制,读写分离 MySQL

125 阅读13分钟

一、什么是主从复制

随着业务的增长,一台数据服务器已经满足不了需求了,负载过重。这个时候就需要减压了,实现负载均衡读写分离,一主一丛或一主多从。 主服务器只负责写,而从服务器只负责读,从而提高了效率减轻压力。 主从复制可以分为:

  • 主从同步:当用户写数据主服务器必须和从服务器同步了才告诉用户写入成功,等待时间比较长。

  • 主从异步:只要用户访问写数据主服务器,立即返回给用户。

  • 主从半同步:当用户访问写数据主服务器写入并同步其中一个从服务器就返回给用户成功。

主从复制的原理

MySQL 主从复制是基于主服务器在二进制日志跟踪所有对数据库的更改。因此,要进行复制,必须在主服务器上启用二进制日志。

每个从服务器从主服务器接收已经记录到日志的数据。当一个从服务器连接到主服务器时,它通知主服务器从服务器日志中读取最后一个更新成功的位置。

从服务器接收从那时发生起的任何更新,并在主机上执行相同的更新。然后封锁等待主服务器通知的更新。

从服务器执行备份不会干扰主服务器,在备份过程中主服务器可以继续处理更新。

主从复制的工作过程

MySQL 的主从复制工作过程大致如下:

  1. 从库生成两个线程,一个 I/O 线程,一个 SQL 线程;
  2. I/O 线程去请求主库的 binlog,并将得到的 binlog 日志写到 relay log(中继日志) 文件中;
  3. 主库会生成一个 log dump 线程,用来给从库 I/O 线程传 binlog;
  4. SQL 线程会读取 relay log 文件中的日志,并解析成具体操作,来实现主从的操作一致,而最终数据一致;

image.png MySQL 建立请求的主从的详细流程如下:

  1. 当从服务器连接主服务器时,主服务器会创建一个 log dump 线程,用于发送 binlog 的内容。在读取 binlog 的内容的操作中,会对象主节点上的 binlog 加锁,当读取完成并发送给从服务器后解锁。
  2. 当从节点上执行 start slave 命令之后,从节点会创建一个 IO 线程用来连接主节点,请求主库中更新 binlog。IO 线程接收主节点 binlog dump 进程发来的更新之后,保存到 relay-log 中。
  3. 从节点 SQL 线程负责读取 realy-log 中的内容,解析成具体的操作执行,最终保证主从数据的一致性。

二、主从复制的形制

1、一主一从

一主一从和一主多从是我们现在见的最多的主从架构,使用起来简单有效,不仅可以实现 HA,而且还能读写分离,进而提升集群的并发能力。

image.png

2、一主多从

image.png

3、多主一从

多主一从可以将多个 MySQL 数据库备份到一台存储性能比较好的服务器上。

image.png

4、双主复制

双主复制,也就是可以互做主从复制,每个 master 既是 master,又是另外一台服务器的 salve。这样任何一方所做的变更,都会通过复制应用到另外一方的数据库中。

image.png

5、级联复制

级联复制模式下,部分 slave 的数据同步不连接主节点,而是连接从节点。 因为如果主节点有太多的从节点,就会损耗一部分性能用于 replication ,那么我们可以让 3~5 个从节点连接主节点,其它从节点作为二级或者三级与从节点连接,这样不仅可以缓解主节点的压力,并且对数据一致性没有负面影响。

image.png

三、主从复制策略

1、异步复制

一个主库,一个或多个从库,数据异步同步到从库。

这种模式下,主节点不会主动推送数据到从节点,主库在执行完客户端提交的事务后会立即将结果返给给客户端,并不关心从库是否已经接收并处理。

这样就会有一个问题,主节点如果崩溃掉了,此时主节点上已经提交的事务可能并没有传到从节点上,如果此时,强行将从提升为主,可能导致新主节点上的数据不完整。

image.png

2、同步复制

在 MySQL cluster 中特有的复制方式。

当主库执行完一个事务,然后所有的从库都复制了该事务并成功执行完才返回成功信息给客户端。

因为需要等待所有从库执行完该事务才能返回成功信息,所以全同步复制的性能必然会收到严重的影响。

3、半同步复制

在异步复制的基础上,确保任何一个主库上的事物在提交之前至少有一个从库已经收到该事物并日志记录下来。

介于异步复制和全同步复制之间,主库在执行完客户端提交的事务后不是立刻返回给客户端,而是等待至少一个从库接收到并写到 relay log 中才返回成功信息给客户端(只能保证主库的 Binlog 至少传输到了一个从节点上),否则需要等待直到超时时间然后切换成异步模式再提交。

相对于异步复制,半同步复制提高了数据的安全性,一定程度的保证了数据能成功备份到从库,同时它也造成了一定程度的延迟,但是比全同步模式延迟要低,这个延迟最少是一个 TCP/IP 往返的时间。所以,半同步复制最好在低延时的网络中使用。

半同步模式不是 MySQL 内置的,从 MySQL 5.5 开始集成,需要 master 和 slave 安装插件开启半同步模式。

image.png

四、读写分离

1、什么是读写分离?

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

2、为什么要读写分离呢?

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

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

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

3、什么时候要读写分离?

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

五、主从复制实验

Master 服务器:192.168.37.101

Slave1 服务器:192.168.37.133

Slave2 服务器:192.168.37.135

1、时间同步

1)主服务器使用本地时钟源

[root@ziyu ~]# systemctl disable --now firewalld
Removed symlink /etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.
[root@ziyu ~]# setenforce 0
[root@ziyu ~]# yum install -y ntp
已加载插件:fastestmirror, langpacks
lzl                                                                              | 3.6 kB  00:00:00
Determining fastest mirrors
软件包 ntp-4.2.6p5-25.el7.centos.2.x86_64 已安装并且是最新版本
无须任何处理
[root@ziyu ~]# vim /etc/ntp.conf
server 127.127.72.0
fudge 127.127.72.0 stratum 8
[root@ziyu ~]# systemctl start ntpd

image.png

image.png

2)两台从服务器同步主服务器的时间

[root@momo ~]# systemctl disable --now firewalld
[root@momo ~]# setenforce 0
[root@momo ~]# yum install -y ntp
已加载插件:fastestmirror, langpacks
itzy                                                                             | 3.6 kB  00:00:00
Determining fastest mirrors
软件包 ntp-4.2.6p5-25.el7.centos.2.x86_64 已安装并且是最新版本
无须任何处理

[root@momo ~]# systemctl start ntpd
[root@momo ~]# /usr/sbin/ntpdate 192.169.37.101
 5 Dec 16:14:38 ntpdate[14872]: the NTP socket is in use, exiting
[root@momo ~]# crontab -e
*/30 * * * * /usr/sbin/ntpdate 192.168.37.101

image.png

image.png

2、主服务器的MySQL配置

[root@ziyu ~]# vim /etc/my.cnf
[mysqld]    #插入下面内容
......  
server-id = 1               #指定服务ID号,master和两台slave都要不同  
log-bin=mysql-bin          #添加,主服务器开启二进制日志 
binlog_format = MIXED       #指定二进制日志(binlog)的记录格式为MIXED  
log-slave-updates=true      #添加,允许slave从master复制数据时可以写入到自己的二进制日志  
expire_logs_days = 7         #设置二进制日志文件过期时间,默认值为0,表示logs不过期  
max_binlog_size = 500M     #设置二进制日志限制大小,如果超出给定值,日志就会发生滚动,默认值是1GB

[root@ziyu ~]# systemctl restart mysqld
[root@ziyu ~]# mysql -u root -p

mysql> grant replication slave on *.* to 'myslave'@'192.168.37.%' identified by '1234';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000002 |      451 |              |                  |                   |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

image.png image.png

image.png

image.png

3、从服务器MySQL配置

[root@momo ~]# 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在同一目录  
relay_log_recovery = 1        #选配项 

[root@momo ~]# systemctl restart mysqld
[root@momo ~]# mysql -u root -p
Enter password:

mysql> CHANGE master to master_host='192.168.37.101',master_user='myslave',master_password='1234',master_log_file='mysql-bin.000002',master_log_pos=451;
Query OK, 0 rows affected, 2 warnings (0.00 sec)

mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.37.101
                  Master_User: myslave
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000002
          Read_Master_Log_Pos: 451
               Relay_Log_File: relay-log-bin.000002
                Relay_Log_Pos: 320
        Relay_Master_Log_File: mysql-bin.000002
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB:

image.png

image.png

image.png

4、验证效果

主服务创建一个库

image.png

在从服务器查看一下

image.png 同步成功

六、读写分离实验

实验环境:

Master 服务器:192.168.37.101

Slave1 服务器:192.168.37.133

Slave2 服务器:192.168.37.135

Amoeba 服务器:192.168.37.134

客户端:192.168.37.201

1、Amoeba服务器配置 192.168.37.134

1)安装 Java 环境

 #先将jdk的二进制文件上传到/opt/目录下,之后复制到/usr/local/目录下
 [root@sana  ~]# cd /opt/
 [root@sana  opt]# cp jdk-6u14-linux-x64.bin /usr/local/
 [root@sana  opt]# cd /usr/local/
 [root@sana  local]# chmod +x jdk-6u14-linux-x64.bin     #为二进制文件增加执行权限
 [root@sana  local]# ./jdk-6u14-linux-x64.bin
 ##按yes,按enter
 ​
 [root@sana  local]# mv jdk1.6.0_14/ /usr/local/jdk1.6     #将jdk目录重命名
 ​

 #添加环境变量
 [root@sana  local]# 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
 ​
 [root@sana  local]# source /etc/profile     #刷新文件,使立即生效
 [root@sana  local]# java -version           #查看jdk版本

image.png

image.png

image.png

2)安装 Amoeba

 #创建Amoeba的解压目录
 [root@sana ~]# mkdir /usr/local/amoeba/
 #将Amoeba安装包上传到/opt/目录,解压安装包
 [root@sana opt]# cd /opt/
 [root@sana opt]# tar zxvf amoeba-mysql-binary-2.2.0.tar.gz -C /usr/local/amoeba/
 #增加目录权限
 [root@sana opt]# chmod -R 755 /usr/local/amoeba/
 #开启Amoeba
 [root@sana opt]# /usr/local/amoeba/bin/amoeba      #如显示amoeba start|stop说明安装成功
 
 #先在Master、Slave1、Slave2 的mysql上开放权限给 Amoeba 访问。注意:这里授权的用户名和密码,会在下一步写入数据库配置文件。
 grant all on *.* to yuji@'192.168.37.%' identified by '1234';
 ​
 #再回到amoeba服务器配置amoeba服务:
 [root@sana opt]# cd /usr/local/amoeba/conf/
 ​
 #备份配置文件,修改amoeba配置文件
 [root@sana conf]# cp amoeba.xml amoeba.xml.bak
 [root@sana conf]# vim amoeba.xml
 --30行--
 <property name="user">amoeba</property>     #30行和32行,授权客户端用于登录amoeba的账号和密码
 --32行-- 
 <property name="password">1234</property>
 ​
 --115行--
 <property name="defaultPool">master</property>  #设置默认服务器池
 --117-去掉注释-
 <property name="writePool">master</property>   #定义写的服务器池名称
 <property name="readPool">slaves</property>    #定义读的服务器池名称
 ​
 ​
 #备份数据库配置文件,之后修改数据库配置文件dbServers.xml
 [root@sana conf]# cp dbServers.xml dbServers.xml.bak
 [root@sana conf]# vim dbServers.xml  
 --23行--注释掉  作用:默认进入test库,注释掉以防mysql中没有test库时,会报错
 <!-- <property name="schema">test</property> -->
 ​
 ##26-30行,此用户就是之前在3台主从服务器上授权的用户,授权amoeba服务器用来登录mysql数据库的用户和密码。
 --26行--修改
 <property name="user">ziyu</property>  
 --28-30行--去掉注释
 <property name="password">1234</property>
 ​
 --45行--修改,设置主服务器的名称master和地址
 <dbServer name="master"  parent="abstractServer">
 --48行--修改,设置主服务器的地址
 <property name="ipAddress">192.168.37.101</property>
 ​
 --52行--修改,设置从服务器1的名称slave1
 <dbServer name="slave1"  parent="abstractServer">
 --55行--修改,设置从服务器1的地址
 <property name="ipAddress">192.168.37.133</property>
 --58行--复制上面6行粘贴,设置从服务器2的名称slave2和地址
 <dbServer name="slave2"  parent="abstractServer">
 <property name="ipAddress">192.168.37.135</property>
 ​
 --65行--修改
 <dbServer name="slaves" virtual="true">
 --71行--修改
 <property name="poolNames">slave1,slave2</property>
 ​
 ​
 [root@sana conf]# /usr/local/amoeba/bin/amoeba start &  #后台启动Amoeba软件,按ctrl+c 返回
 [root@sana conf]# netstat -anpt | grep java            #查看8066端口是否开启,默认端口为TCP 8066

image.png

image.png

2、客户端安装mariadb数据库

 [root@ziyu ~]# yum install -y mariadb-server mariadb   #安装mariadb数据库
 [root@ziyu ~]# systemctl start mariadb.service         #启动mariadb
 ​
 ​
 #客户端通过amoeba服务器登录数据库,之后向库中写入数据:
 mysql -u amoeba -p1234 -h 192.168.37.101 -P8066        
 use aa1;
 create table class(id int,name char(10));
 #通过amoeba服务器代理访问mysql ,再通过客户端连接mysql后写入的数据只有主服务会记录,然后同步给从--从服务器

image.png

3、测试读写分离

 //在两台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 aa1;
 select * from class;        
 #客户端会分别向slave1和slave2读取数据(轮询),显示的只有在两个从服务器上添加的数据,没有在主服务器上添加的数据。说明读写是分离的,只从slave中读取数据。
 ​
 insert into class values('4','qianliu',);    //客户端插入数据,只有主服务器上有此数据
 ​
 //在两个从服务器上执行 start slave; 即可实现同步主服务器中添加的数据
 start slave;             #开启同步
 select * from class;    

七、总结

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线程将进入睡眠状态,等待下一次被唤醒。