深刻MySQL复制(一)

本文很是详细地介绍MySQL复制相关的内容,包括基本概念、复制原理、如何配置不一样类型的复制(传统复制)等等。在此文章以后,还有几篇文章分别介绍GTID复制、半同步复制、实现MySQL的动静分离,以及MySQL 5.7.17引入的革命性功能:组复制(MGR)。html

本文是MySQL Replication的基础,但却很是重要。对于MySQL复制,如何搭建它不是重点(由于简单,网上资源很是多),如何维护它才是重点(网上资源不集中)。如下几个知识点是掌握MySQL复制所必备的:mysql

  1. 复制的原理
  2. 将master上已存在的数据恢复到slave上做为基准数据
  3. 获取正确的binlog坐标
  4. 深刻理解show slave status中的一些状态信息

本文对以上内容都作了很是详细的说明。但愿对各位初学、深刻MySQL复制有所帮助。算法

mysql replication官方手册:https://dev.mysql.com/doc/refman/5.7/en/replication.html。sql

1.复制的基本概念和原理

mysql复制是指从一个mysql服务器(MASTER)将数据经过日志的方式通过网络传送到另外一台或多台mysql服务器(SLAVE),而后在slave上重放(replay或redo)传送过来的日志,以达到和master数据同步的目的。shell

它的工做原理很简单。首先确保master数据库上开启了二进制日志,这是复制的前提数据库

  • 在slave准备开始复制时,首先要执行change master to语句设置链接到master服务器的链接参数,在执行该语句的时候要提供一些信息,包括如何链接和要从哪复制binlog,这些信息在链接的时候会记录到slave的datadir下的master.info文件中,之后再链接master的时候将不用再提供这新信息而是直接读取该文件进行链接。
  • 在slave上有两种线程,分别是IO线程和SQL线程
    • IO线程用于链接master,监控和接受master的binlog。当启动IO线程成功链接master时,master会同时启动一个dump线程,该线程将slave请求要复制的binlog给dump出来,以后IO线程负责监控并接收master上dump出来的二进制日志,当master上binlog有变化的时候,IO线程就将其复制过来并写入到本身的中继日志(relay log)文件中。
    • slave上的另外一个线程SQL线程用于监控、读取并重放relay log中的日志,将数据写入到本身的数据库中。以下图所示。

站在slave的角度上看,过程以下:缓存

站在master的角度上看,过程以下(默认的异步复制模式,前提是设置了sync_binlog=1,不然binlog刷盘时间由操做系统决定):安全

因此,能够认为复制大体有三个步骤:服务器

  1. 数据修改写入master数据库的binlog中。
  2. slave的IO线程复制这些变更的binlog到本身的relay log中。
  3. slave的SQL线程读取并从新应用relay log到本身的数据库上,让其和master数据库保持一致。

从复制的机制上能够知道,在复制进行前,slave上必须具备master上部分完整内容做为复制基准数据。例如,master上有数据库A,二进制日志已经写到了pos1位置,那么在复制进行前,slave上必需要有数据库A,且若是要从pos1位置开始复制的话,还必须有和master上pos1以前彻底一致的数据。若是不知足这样的一致性条件,那么在replay中继日志的时候将不知道如何进行应用而致使数据混乱。也就是说,复制是基于binlog的position进行的,复制以前必须保证position一致。(注:这是传统的复制方式所要求的)网络

能够选择对哪些数据库甚至数据库中的哪些表进行复制。默认状况下,MySQL的复制是异步的。slave能够不用一直连着master,即便中间断开了也能从断开的position处继续进行复制。

MySQL 5.6对比MySQL 5.5在复制上进行了很大的改进,主要包括支持GTID(Global Transaction ID,全局事务ID)复制和多SQL线程并行重放。GTID的复制方式和传统的复制方式不同,经过全局事务ID,它不要求复制前slave有基准数据,也不要求binlog的position一致。

MySQL 5.7.17则提出了组复制(MySQL Group Replication,MGR)的概念。像数据库这样的产品,必需要尽量完美地设计一致性问题,特别是在集群、分布式环境下。Galera就是一个MySQL集群产品,它支持多主模型(多个master),可是当MySQL 5.7.17引入了MGR功能后,Galera的优点再也不明显,甚至MGR能够取而代之。MGR为MySQL集群中多主复制的不少问题提供了很好的方案,可谓是一项革命性的功能。

复制和二进制日志息息相关,因此学习本章必须先有二进制日志的相关知识。

2.复制的好处

围绕下面的拓扑图来分析:

主要有如下几点好处:

1.提供了读写分离的能力。

replication让全部的slave都和master保持数据一致,所以外界客户端能够从各个slave中读取数据,而写数据则从master上操做。也就是实现了读写分离。

须要注意的是,为了保证数据一致性,写操做必须在master上进行

一般说到读写分离这个词,马上就能意识到它会分散压力、提升性能。

2.为MySQL服务器提供了良好的伸缩(scale-out)能力。

因为各个slave服务器上只提供数据检索而没有写操做,所以"随意地"增长slave服务器数量来提高整个MySQL群的性能,而不会对当前业务产生任何影响。

之因此"随意地"要加上双引号,是由于每一个slave都要和master创建链接,传输数据。若是slave数量巨多,master的压力就会增大,网络带宽的压力也会增大。

3.数据库备份时,对业务影响降到最低。

因为MySQL服务器群中全部数据都是一致的(至少几乎是一致的),因此在须要备份数据库的时候能够任意中止某一台slave的复制功能(甚至中止整个mysql服务),而后从这台主机上进行备份,这样几乎不会影响整个业务(除非只有一台slave,但既然只有一台slave,说明业务压力并不大,短时间内将这个压力分配给master也不会有什么影响)。

4.能提高数据的安全性。

这是显然的,任意一台mysql服务器断开,都不会丢失数据。即便是master宕机,也只是丢失了那部分尚未传送的数据(异步复制时才会丢失这部分数据)。

5.数据分析再也不影响业务。

须要进行数据分析的时候,直接划分一台或多台slave出来专门用于数据分析。这样OLTP和OLAP能够共存,且几乎不会影响业务处理性能。

3.复制分类和它们的特性

MySQL支持两种不一样的复制方法:传统的复制方式和GTID复制。MySQL 5.7.17以后还支持组复制(MGR)。

  • (1).传统的复制方法要求复制以前,slave上必须有基准数据,且binlog的position一致。
  • (2).GTID复制方法不要求基准数据和binlog的position一致性。GTID复制时,master上只要一提交,就会当即应用到slave上。这极大地简化了复制的复杂性,且更好地保证master上和各slave上的数据一致性。

从数据同步方式的角度考虑,MySQL支持4种不一样的同步方式:同步(synchronous)、半同步(semisynchronous)、异步(asynchronous)、延迟(delayed)。因此对于复制来讲,就分为同步复制、半同步复制、异步复制和延迟复制。

3.1 同步复制

客户端发送DDL/DML语句给master,master执行完毕后还须要等待全部的slave都写完了relay log才认为这次DDL/DML成功,而后才会返回成功信息给客户端。同步复制的问题是master必须等待,因此延迟较大,在MySQL中不使用这种复制方式。

例如上图中描述的,只有3个slave全都写完relay log并返回ACK给master后,master才会判断这次DDL/DML成功。

3.2 半同步复制

客户端发送DDL/DML语句给master,master执行完毕后还要等待一个slave写完relay log并返回确认信息给master,master才认为这次DDL/DML语句是成功的,而后才会发送成功信息给客户端。半同步复制只需等待一个slave的回应,且等待的超时时间能够设置,超时后会自动降级为异步复制,因此在局域网内(网络延迟很小)使用半同步复制是可行的。

例如上图中,只有第一个slave返回成功,master就判断这次DDL/DML成功,其余的slave不管复制进行到哪个阶段都可有可无。

3.3 异步复制

客户端发送DDL/DML语句给master,master执行完毕当即返回成功信息给客户端,而无论slave是否已经开始复制。这样的复制方式致使的问题是,当master写完了binlog,而slave尚未开始复制或者复制还没完成时,slave上和master上的数据暂时不一致,且此时master忽然宕机,slave将会丢失一部分数据。若是此时把slave提高为新的master,那么整个数据库就永久丢失这部分数据。

3.4 延迟复制

顾名思义,延迟复制就是故意让slave延迟一段时间再从master上进行复制。

4.配置一主一从

此处先配置默认的异步复制模式。因为复制和binlog息息相关,若是对binlog还不熟悉,请先了解binlog,见:详细分析二进制日志

mysql支持一主一从和一主多从。可是每一个slave必须只能是一个master的从,不然从多个master接受二进制日志后重放将会致使数据混乱的问题。

如下是一主一从的结构图:

在开始传统的复制(非GTID复制)前,须要完成如下几个关键点,这几个关键点指导后续复制的全部步骤

  1. 为master和slave设定不一样的server-id,这是主从复制结构中很是关键的标识号。到了MySQL 5.7,彷佛不设置server id就没法开启binlog。设置server id须要重启MySQL实例。
  2. 开启master的binlog。刚安装并初始化的MySQL默认未开启binlog,建议手动设置binlog且为其设定文件名,不然默认以主机名为基名时修改主机名后会找不到日志文件。
  3. 最好设置master上的变量sync_binlog=1(MySQL 5.7.7以后默认为1,以前的版本默认为0),这样每写一次二进制日志都将其刷新到磁盘,让slave服务器能够尽快地复制。防止万一master的二进制日志还在缓存中就宕机时,slave没法复制这部分丢失的数据。
  4. 最好设置master上的redo log的刷盘变量innodb_flush_log_at_trx_commit=1(默认值为1),这样每次提交事务都会当即将事务刷盘保证持久性和一致性。
  5. 在slave上开启中继日志relay log。这个是默认开启的,一样建议手动设置其文件名。
  6. 建议在master上专门建立一个用于复制的用户,它只须要有复制权限replication slave用来读取binlog。
  7. 确保slave上的数据和master上的数据在"复制的起始position以前"是彻底一致的。若是master和slave上数据不一致,复制会失败。
  8. 记下master开始复制前binlog的position,由于在slave链接master时须要指定从master的哪一个position开始复制。
  9. 考虑是否将slave设置为只读,也就是开启read_only选项。这种状况下,除了具备super权限(mysql 5.7.16还提供了super_read_only禁止super的写操做)和SQL线程能写数据库,其余用户都不能进行写操做。这种禁写对于slave来讲,绝大多数场景都很是适合。

4.1 一主一从

一主一从是最简单的主从复制结构。本节实验环境以下:

  1. 配置master和slave的配置文件。
[mysqld]          # master
datadir=/data
socket=/data/mysql.sock
log-bin=master-bin
sync-binlog=1
server-id=100
[mysqld]       # slave
datadir=/data
socket=/data/mysql.sock
relay-log=slave-bin
server-id=111
  1. 重启master和slave上的MySQL实例。
service mysqld restart
  1. 在master上建立复制专用的用户。
create user 'repl'@'192.168.100.%' identified by 'P@ssword1!';
grant REPLICATION SLAVE on *.* to 'repl'@'192.168.100.%';
  1. 将slave恢复到master上指定的坐标。
    这是备份恢复的内容,此处用一个小节来简述操做过程。详细内容见MySQL备份和恢复(一)、(二)、(三)

4.2 将slave恢复到master指定的坐标

对于复制而言,有几种状况:

  • (1).待复制的master没有新增数据,例如新安装的mysql实例。这种状况下,能够跳过恢复这个过程。
  • (2).待复制的master上已有数据。这时须要将这些已有数据也应用到slave上,并获取master上binlog当前的坐标。只有slave和master的数据能匹配上,slave重放relay log时才不会出错。

第一种状况此处不赘述。第二种状况有几种方法,例如使用mysqldump、冷备份、xtrabackup等工具,这其中又须要考虑是MyISAM表仍是InnoDB表。


在实验开始以前,首先在master上新增一些测试数据,以innodb和myisam的数值辅助表为例。

DROP DATABASE IF EXISTS backuptest;
CREATE DATABASE backuptest;
USE backuptest;

# 建立myisam类型的数值辅助表和插入数据的存储过程
CREATE TABLE num_isam (n INT NOT NULL PRIMARY KEY) ENGINE = MYISAM ;

DROP PROCEDURE IF EXISTS proc_num1;
DELIMITER $$
CREATE PROCEDURE proc_num1 (num INT) 
BEGIN
    DECLARE rn INT DEFAULT 1 ;
    TRUNCATE TABLE backuptest.num_isam ;
    INSERT INTO backuptest.num_isam VALUES(1) ;
    dd: WHILE rn * 2 < num DO 
        BEGIN
            INSERT INTO backuptest.num_isam 
            SELECT rn + n FROM backuptest.num_isam;
            SET rn = rn * 2 ;
        END ;
    END WHILE dd;
    INSERT INTO backuptest.num_isam 
    SELECT n + rn 
    FROM backuptest.num_isam 
    WHERE n + rn <= num;
END ;
$$
DELIMITER ;

# 建立innodb类型的数值辅助表和插入数据的存储过程
CREATE TABLE num_innodb (n INT NOT NULL PRIMARY KEY) ENGINE = INNODB ;

DROP PROCEDURE IF EXISTS proc_num2;
DELIMITER $$
CREATE PROCEDURE proc_num2 (num INT) 
BEGIN
    DECLARE rn INT DEFAULT 1 ;
    TRUNCATE TABLE backuptest.num_innodb ;
    INSERT INTO backuptest.num_innodb VALUES(1) ;
    dd: WHILE rn * 2 < num DO 
        BEGIN
            INSERT INTO backuptest.num_innodb 
            SELECT rn + n FROM backuptest.num_innodb;
            SET rn = rn * 2 ;
        END ;
    END WHILE dd;
    INSERT INTO backuptest.num_innodb 
    SELECT n + rn 
    FROM backuptest.num_innodb 
    WHERE n + rn <= num ;
END ;
$$
DELIMITER ;

# 分别向两个数值辅助表中插入100W条数据
CALL proc_num1 (1000000) ;
CALL proc_num2 (1000000) ;

所谓数值辅助表是只有一列的表,且这个字段的值全是数值,从1开始增加。例如上面的是从1到100W的数值辅助表。

mysql> select * from backuptest.num_isam limit 10;
+----+
| n  |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
|  6 |
|  7 |
|  8 |
|  9 |
| 10 |
+----+

4.2.1 获取master binlog的坐标

若是master是全新的数据库实例,或者在此以前没有开启过binlog,那么它的坐标位置是position=4。之因此是4而非0,是由于binlog的前4个记录单元是每一个binlog文件的头部信息。

若是master已有数据,或者说master之前就开启了binlog并写过数据库,那么须要手动获取position。为了安全以及没有后续写操做,必须先锁表。

mysql> flush tables with read lock;

注意,此次的锁表会致使写阻塞以及innodb的commit操做。

而后查看binlog的坐标。

mysql> show master status;   # 为了排版,简化了输出结果
+-------------------+----------+--------------+--------+--------+
| File              | Position | Binlog_Do_DB | ...... | ...... |
+-------------------+----------+--------------+--------+--------+
| master-bin.000001 |      623 |              |        |        |
+-------------------+----------+--------------+--------+--------+

记住master-bin.000001和623。

4.2.2 备份master数据到slave上

下面给出3种备份方式以及对应slave的恢复方法。建议备份全部库到slave上,若是要筛选一部分数据库或表进行复制,应该在slave上筛选(筛选方式见后文筛选要复制的库和表),而不该该在master的备份过程当中指定。

  • 方式一:冷备份直接cp。这种状况只适用于没有新写入操做。严谨一点,只适合拷贝完成前master不能有写入操做。
  1. 若是要复制全部库,那么直接拷贝整个datadir。
  2. 若是要复制的是某个或某几个库,直接拷贝相关目录便可。但注意,这种冷备份的方式只适合MyISAM表和开启了innodb_file_per_table=ON的InnoDB表。若是没有开启该变量,innodb表使用公共表空间,没法直接冷备份。
  3. 若是要冷备份innodb表,最安全的方法是先关闭master上的mysql,而不是经过表锁。

因此,若是没有涉及到innodb表,那么在锁表以后,能够直接冷拷贝。最后释放锁。

mysql> flush tables with read lock;
mysql> show master status;   # 为了排版,简化了输出结果
+-------------------+----------+--------------+--------+--------+
| File              | Position | Binlog_Do_DB | ...... | ...... |
+-------------------+----------+--------------+--------+--------+
| master-bin.000001 |      623 |              |        |        |
+-------------------+----------+--------------+--------+--------+
shell> rsync -avz /data 192.168.100.150:/
mysql> unlock tables;

此处实验,假设要备份的是整个实例,由于涉及到了innodb表,因此建议关闭MySQL。由于是冷备份,因此slave上也应该关闭MySQL。

# master和slave上都执行
shell> mysqladmin -uroot -p shutdown

而后将整个datadir拷贝到slave上(固然,有些文件是不用拷贝的,好比master上的binlog、mysql库等)。

# 将master的datadir(/data)拷贝到slave的datadir(/data)
shell> rsync -avz /data 192.168.100.150:/

须要注意,在冷备份的时候,须要将备份到目标主机上的DATADIR/auto.conf删除,这个文件中记录的是mysql server的UUID,而master和slave的UUID必须不能一致。

而后重启master和slave。由于重启了master,因此binlog已经滚动了,不过此次不用再查看binlog坐标,由于重启形成的binlog日志移动不会影响slave。

  • 方式二:使用mysqldump进行备份恢复。

这种方式简单的多,并且对于innodb表很适用,可是slave上恢复时速度慢,由于恢复时数据全是经过insert插入的。由于mysqldump能够进行定时点恢复甚至记住binlog的坐标,因此无需再手动获取binlog的坐标。

shell> mysqldump -uroot -p --all-databases --master-data=2 >dump.db

注意,--master-data选项将再dump.db中加入change master to相关的语句,值为2时,change master to语句是注释掉的,值为1或者没有提供值时,这些语句是直接激活的。同时,--master-data会锁定全部表(若是同时使用了--single-transaction,则不是锁全部表,详细内容请参见mysqldump)。

所以,能够直接从dump.db中获取到binlog的坐标。记住这个坐标。

[root@xuexi ~]# grep -i -m 1 'change master to' dump.db 
-- CHANGE MASTER TO MASTER_LOG_FILE='master-bin.000002', MASTER_LOG_POS=154;

而后将dump.db拷贝到slave上,使用mysql执行dump.db脚本便可。也能够直接在master上远程链接到slave上执行。例如:

shell> mysql -uroot -p -h 192.168.100.150 -e 'source dump.db'
  • 方式三:使用xtrabackup进行备份恢复。

这是三种方式中最佳的方式,安全性高、速度快。由于xtrabackup备份的时候会记录master的binlog的坐标,所以也无需手动获取binlog坐标。

xtrabackup详细的备份方法见:xtrabackup

注意:master和slave上都安装percona-xtrabackup。

以全备份为例:

innobackupex -u root -p /backup

备份完成后,在/backup下生成一个以时间为名称的目录。其内文件以下:

[root@xuexi ~]# ll /backup/2018-05-29_04-12-15
total 77872
-rw-r----- 1 root root      489 May 29 04:12 backup-my.cnf
drwxr-x--- 2 root root     4096 May 29 04:12 backuptest
-rw-r----- 1 root root     1560 May 29 04:12 ib_buffer_pool
-rw-r----- 1 root root 79691776 May 29 04:12 ibdata1
drwxr-x--- 2 root root     4096 May 29 04:12 mysql
drwxr-x--- 2 root root     4096 May 29 04:12 performance_schema
drwxr-x--- 2 root root    12288 May 29 04:12 sys
-rw-r----- 1 root root       22 May 29 04:12 xtrabackup_binlog_info
-rw-r----- 1 root root      115 May 29 04:12 xtrabackup_checkpoints
-rw-r----- 1 root root      461 May 29 04:12 xtrabackup_info
-rw-r----- 1 root root     2560 May 29 04:12 xtrabackup_logfile

其中xtrabackup_binlog_info中记录了binlog的坐标。记住这个坐标。

[root@xuexi ~]# cat /backup/2018-05-29_04-12-15/xtrabackup_binlog_info 
master-bin.000002       154

而后将备份的数据执行"准备"阶段。这个阶段不要求链接mysql,所以不用给链接选项。

innobackupex --apply-log /backup/2018-05-29_04-12-15

最后,将/backup目录拷贝到slave上进行恢复。恢复的阶段就是向MySQL的datadir拷贝。但注意,xtrabackup恢复阶段要求datadir必须为空目录。不然报错:

[root@xuexi ~]# innobackupex --copy-back /backup/2018-05-29_04-12-15/
180529 23:54:27 innobackupex: Starting the copy-back operation

IMPORTANT: Please check that the copy-back run completes successfully.
           At the end of a successful copy-back run innobackupex
           prints "completed OK!".

innobackupex version 2.4.11 based on MySQL server 5.7.19 Linux (x86_64) (revision id: b4e0db5)
Original data directory /data is not empty!

因此,中止slave的mysql并清空datadir。

service mysqld stop
rm -rf /data/*

恢复时使用的模式是"--copy-back",选项后指定要恢复的源备份目录。恢复时由于不须要链接数据库,因此不用指定链接选项。

[root@xuexi ~]# innobackupex --copy-back /backup/2018-05-29_04-12-15/
180529 23:55:53 completed OK!

恢复完成后,MySQL的datadir的文件的全部者和属组是innobackupex的调用者,因此须要改回mysql.mysql。

shell> chown -R mysql.mysql /data

启动slave,并查看恢复是否成功。

shell> service mysqld start
shell> mysql -uroot -p -e 'select * from backuptest.num_isam limit 10;'
+----+
| n  |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
|  6 |
|  7 |
|  8 |
|  9 |
| 10 |
+----+

4.3 slave开启复制

通过前面的一番折腾,总算是把该准备的数据都准备到slave上,也获取到master上binlog的坐标(154)。如今还欠东风:链接master。

链接master时,须要使用change master to提供链接到master的链接选项,包括user、port、password、binlog、position等。

mysql> change master to 
        master_host='192.168.100.20',
        master_port=3306,
        master_user='repl',
        master_password='P@ssword1!',
        master_log_file='master-bin.000002',
        master_log_pos=154;

完整的change master to语法以下:

CHANGE MASTER TO option [, option] ...
option:
  | MASTER_HOST = 'host_name'
  | MASTER_USER = 'user_name'
  | MASTER_PASSWORD = 'password'
  | MASTER_PORT = port_num
  | MASTER_LOG_FILE = 'master_log_name'
  | MASTER_LOG_POS = master_log_pos
  | MASTER_AUTO_POSITION = {0|1}
  | RELAY_LOG_FILE = 'relay_log_name'
  | RELAY_LOG_POS = relay_log_pos
  | MASTER_SSL = {0|1}
  | MASTER_SSL_CA = 'ca_file_name'
  | MASTER_SSL_CAPATH = 'ca_directory_name'
  | MASTER_SSL_CERT = 'cert_file_name'
  | MASTER_SSL_CRL = 'crl_file_name'
  | MASTER_SSL_CRLPATH = 'crl_directory_name'
  | MASTER_SSL_KEY = 'key_file_name'
  | MASTER_SSL_CIPHER = 'cipher_list'
  | MASTER_SSL_VERIFY_SERVER_CERT = {0|1}

而后,启动IO线程和SQL线程。能够一次性启动两个,也能够分开启动。

# 一次性启动、关闭
start slave;
stop slave;

# 单独启动
start slave io_thread;
start slave sql_thread;

至此,复制就已经能够开始工做了。当master写入数据,slave就会从master处进行复制。

例如,在master上新建一个表,而后去slave上查看是否有该表。由于是DDL语句,它会写二进制日志,因此它也会复制到slave上。

4.4 查看slave的信息

change master to后,在slave的datadir下就会生成master.info文件和relay-log.info文件,这两个文件随着复制的进行,其内数据会随之更新。

4.4.1 master.info

master.info文件记录的是IO线程相关的信息,也就是链接master以及读取master binlog的信息。经过这个文件,下次链接master时就不须要再提供链接选项。

如下是master.info的内容,每一行的意义见官方手册

[root@xuexi ~]# cat /data/master.info 
25                        # 本文件的行数
master-bin.000002         # IO线程正从哪一个master binlog读取日志
154                       # IO线程读取到master binlog的位置
192.168.100.20            # master_host
repl                      # master_user
P@ssword1!                # master_password
3306                      # master_port
60                        # master_retry,slave重连master的超时时间(单位秒)
0





0
30.000

0

86400


0

4.4.2 relay-log.info

relay-log.info文件中记录的是SQL线程相关的信息。如下是relay-log.info文件的内容,每一行的意义见官方手册

[root@xuexi ~]# cat /data/relay-log.info 
7                   # 本文件的行数
./slave-bin.000001  # 当前SQL线程正在读取的relay-log文件
4                   # SQL线程已执行到的relay log位置
master-bin.000002   # SQL线程最近执行的操做对应的是哪一个master binlog
154                 # SQL线程最近执行的操做对应的是master binlog的哪一个位置
0                   # slave上必须落后于master多长时间
0                   # 正在运行的SQL线程数
1                   # 一种用于内部信息交流的ID,目前值老是1

4.4.3 show slave status

在slave上执行show slave status能够查看slave的状态信息。信息很是多,每一个字段的详细意义可参见官方手册

mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State:        # slave上IO线程的状态,来源于show processlist
                  Master_Host: 192.168.100.20
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: master-bin.000002
          Read_Master_Log_Pos: 154
               Relay_Log_File: slave-bin.000001
                Relay_Log_Pos: 4
        Relay_Master_Log_File: master-bin.000002
             Slave_IO_Running: No          # IO线程的状态,此处为未运行且未链接状态
            Slave_SQL_Running: No          # SQL线程的状态,此处为未运行状态
              Replicate_Do_DB:             # 显式指定要复制的数据库
          Replicate_Ignore_DB:             # 显式指定要忽略的数据库
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table:          # 以通配符方式指定要复制的表
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 154
              Relay_Log_Space: 154
              Until_Condition: None     # start slave语句中指定的until条件,
                                        # 例如,读取到哪一个binlog位置就中止
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: NULL    # SQL线程执行过的位置比IO线程慢多少
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 0      # master的server id
                  Master_UUID: 
             Master_Info_File: /data/master.info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State:             # slave SQL线程的状态
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 
     Last_SQL_Error_Timestamp: 
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: 
                Auto_Position: 0
         Replicate_Rewrite_DB: 
                 Channel_Name: 
           Master_TLS_Version: 
1 row in set (0.01 sec)

由于太长,后面再列出show slave status时,将裁剪一些意义不大的行。

再次回到上面show slave status的信息。除了那些描述IO线程、SQL线程状态的行,还有几个log_file和pos相关的行,以下所列。

Master_Log_File: master-bin.000002
  Read_Master_Log_Pos: 154
       Relay_Log_File: slave-bin.000001
        Relay_Log_Pos: 4
Relay_Master_Log_File: master-bin.000002
  Exec_Master_Log_Pos: 154

理解这几行的意义相当重要,前面由于排版限制,描述看上去有些重复。因此这里完整地描述它们:

  • Master_Log_File:IO线程正在读取的master binlog;
  • Read_Master_Log_Pos:IO线程已经读取到master binlog的哪一个位置;
  • Relay_Log_File:SQL线程正在读取和执行的relay log;
  • Relay_Log_Pos:SQL线程已经读取和执行到relay log的哪一个位置;
  • Relay_Master_Log_File:SQL线程最近执行的操做对应的是哪一个master binlog;
  • Exec_Master_Log_Pos:SQL线程最近执行的操做对应的是master binlog的哪一个位置。

因此,(Relay_Master_Log_File, Exec_Master_log_Pos)构成一个坐标,这个坐标表示slave上已经将master上的哪些数据重放到本身的实例中,它能够用于下一次change master to时指定的binlog坐标。

与这个坐标相对应的是slave上SQL线程的relay log坐标(Relay_Log_File, Relay_Log_Pos)。这两个坐标位置不一样,但它们对应的数据是一致的。

最后还有一个延迟参数Seconds_Behind_Master须要说明一下,它的本质意义是SQL线程比IO线程慢多少。若是master和slave之间的网络情况优良,那么slave的IO线程读速度和master写binlog的速度基本一致,因此这个参数也用来描述"SQL线程比master慢多少",也就是说slave比master少多少数据,只不过衡量的单位是秒。但须要注意,这个参数的描述并不标准,只有在网速很好的时候作个大概估计,不少种状况下它的值都是0,即便SQL线程比IO线程慢了不少也是如此。

4.4.4 slave信息汇总

上面的master.info、relay-log.info和show slave status的状态都是刚链接上master还未启动IO thread、SQL thread时的状态。下面将显示已经进行一段正在执行复制的slave状态。

首先查看启动io thread和sql thread后的状态。使用show processlist查看便可。

mysql> start slave;

mysql> show processlist;   # slave上的信息,为了排版,简化了输出
+----+-------------+---------+--------------------------------------------------------+
| Id | User        | Command | State                                                  |
+----+-------------+---------+--------------------------------------------------------+
|  4 | root        | Sleep   |                                                        |
|  7 | root        | Query   | starting                                               |
|  8 | system user | Connect | Waiting for master to send event                       |
|  9 | system user | Connect | Slave has read all relay log; waiting for more updates |
+----+-------------+---------+--------------------------------------------------------+

能够看到:

  • Id=8的线程负责链接master并读取binlog,它是IO 线程,它的状态指示"等待master发送更多的事件";
  • Id=9的线程负责读取relay log,它是SQL线程,它的状态指示"已经读取了全部的relay log"。

再看看此时master上的信息。

mysql> show processlist;        # master上的信息,为了排版,通过了修改
+----+------+-----------------------+-------------+--------------------------------------+
| Id | User | Host                  | Command     | State                                |
+----+------+-----------------------+-------------+--------------------------------------+
|  4 | root | localhost             | Query       | starting                             |
|----|------|-----------------------|-------------|--------------------------------------|
| 16 | repl | 192.168.100.150:39556 | Binlog Dump | Master has sent all binlog to slave; |
|    |      |                       |             | waiting for more updates             |
+----+------+-----------------------+-------------+--------------------------------------+

master上有一个Id=16的binlog dump线程,该线程的用户是repl。它的状态指示"已经将全部的binlog发送给slave了"。

如今,在master上执行一个长事件,以便查看slave上的状态信息。

仍然使用前面插入数值辅助表的存储过程,此次分别向两张表中插入一亿条数据(尽管去抽烟、喝茶,够等几分钟的。若是机器性能很差,请大幅减小插入的行数)。

call proc_num1(100000000);
call proc_num2(100000000);

而后去slave上查看信息,以下。由于太长,已经裁剪了一部分没什么用的行。

mysql> show slave status\G
mysql: [Warning] Using a password on the command line interface can be insecure.
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.100.20
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: master-bin.000003
          Read_Master_Log_Pos: 512685413
               Relay_Log_File: slave-bin.000003
                Relay_Log_Pos: 336989434
        Relay_Master_Log_File: master-bin.000003
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
          Exec_Master_Log_Pos: 336989219
      Slave_SQL_Running_State: Reading event from the relay log

从中获取到的信息有:

  1. IO线程的状态
  2. SQL线程的状态
  3. IO线程读取到master binlog的哪一个位置:512685413
  4. SQL线程已经执行到relay log的哪一个位置:336989434
  5. SQL线程执行的位置对应于master binlog的哪一个位置:336989219

能够看出,IO线程比SQL线程超前了不少不少,因此SQL线程比IO线程的延迟较大。

4.5 MySQL复制如何实现断开重连

不少人觉得change master to语句是用来链接master的,实际上这种说法是错的。链接master是IO线程的事情,change master to只是为IO线程链接master时提供链接参数。

若是slave历来没链接过master,那么必须使用change master to语句来生成IO线程所须要的信息,这些信息记录在master.info中。这个文件是change master to成功以后当即生成的,之后启动IO线程时,IO线程都会自动读取这个文件来链接master,不须要先执行change master to语句。

例如,能够随时stop slave来中止复制线程,而后再随时start slave,只要master.info存在,且没有人为修改过它,IO线程就必定不会出错。这是由于master.info会随着IO线程的执行而更新,不管读取到master binlog的哪一个位置,都会记录下这个位置,如此一来,IO线程下次启动的时候就知道从哪里开始监控master binlog。

前面还提到一个文件:relay-log.info文件。这个文件中记录的是SQL线程的相关信息,包括读取、执行到relay log的哪一个位置,刚重放的数据对应master binlog的哪一个位置。随着复制的进行,这个文件的信息会即时改变。因此,经过relay-log.info,下次SQL线程启动的时候就能知道从relay log的哪一个地方继续读取、执行。

若是不当心把relay log文件删除了,SQL线程可能会丢失了一部分相比IO线程延迟的数据。这时候,只需将relay-log.info中第四、5行记录的"SQL线程刚重放的数据对应master binlog的坐标"手动修改到master.info中便可,这样IO线程下次链接master就会从master binlog的这个地方开始监控。固然,也能够将这个坐标做为change master to的坐标来修改master.info。

此外,当mysql实例启动时,默认会自动start slave,也就是MySQL一启动就自动开启复制线程。若是想要禁止这种行为,在配置文件中加上:

[mysqld]
skip-slave-start

4.6 一些变量

默认状况下,slave链接到master后会在slave的datadir下生成master.info和relay-log.info文件,可是这是能够经过设置变量来改变的。

  • master-info-repository={TABLE|FILE}:master的信息是记录到文件master.info中仍是记录到表mysql.slave_master_info中。默认为file。
  • relay-log-info-repository={TABLE|FILE}:slave的信息是记录到文件relay-log.info中仍是记录到表mysql.slave_relay_log_info中。默认为file。

IO线程每次从master复制日志要写入到relay log中,可是它是先放在内存的,等到必定时机后才会将其刷到磁盘上的relay log文件中。刷到磁盘的时机能够由变量控制。

另外,IO线程每次从master复制日志后都会更新master.info的信息,也是先更新内存中信息,在特定的时候才会刷到磁盘的master.info文件;同理SQL线程更新realy-log.info也是同样的。它们是能够经过变量来设置更新时机的。

  • sync-relay-log=N:设置为大于0的数表示每从master复制N个事件就刷一次盘。设置为0表示依赖于操做系统的sync机制。
  • sync-master-info=N:依赖于master-info-repository的设置,若是为file,则设置为大于0的值时表示每更新多少次master.info将其写入到磁盘的master.info中,设置为0则表示由操做系统来决定什么时候调用fdatasync()函数刷到磁盘。若是设置为table,则设置为大于0的值表示每更新多少次master.info就更新mysql.slave_master_info表一次,若是设置为0则表示永不更新该表。
  • sync-relay-log-info=N:同上。

5.一主多从

一主多从有两种状况,结构图以下。

如下是一主多从的结构图(和一主一从的配置方法彻底一致):

如下是一主多从,但某slave是另外一群MySQL实例的master:

配置一主多从时,须要考虑一件事:slave上是否要开启binlog? 若是不开启slave的binlog,性能确定要稍微好一点。可是开启了binlog后,能够经过slave来备份数据,也能够在master宕机时直接将slave切换为新的master。此外,若是是上面第二种主从结构,这台slave必须开启binlog。能够将某台或某几台slave开启binlog,并在mysql动静分离的路由算法上稍微减小一点到这些slave上的访问权重。

上面第一种一主多从的结构没什么可解释的,它和一主一从的配置方式彻底同样,可是能够考虑另外一种状况:向现有主从结构中添加新的slave。因此,稍后先介绍这种添加slave,再介绍第二种一主多从的结构。

5.1 向现有主从结构中添加slave

官方手册:https://dev.mysql.com/doc/refman/5.7/en/replication-howto-additionalslaves.html

例如在前文一主一从的实验环境下添加一台新的slave。

由于新的slave在开始复制前,要有master上的基准数据,还要有master binlog的坐标。按照前文一主一从的配置方式,固然很容易获取这些信息,但这样会将master锁住一段时间(由于要备份基准数据)。

深刻思考一下,其实slave上也有数据,还有relay log以及一些仓库文件标记着数据复制到哪一个地方。因此,彻底能够从slave上获取基准数据和坐标,也建议这样作

仍然有三种方法从slave上获取基准数据:冷备份、mysqldump和xtrabackup。方法见前文将slave恢复到master指定的坐标

其实临时关闭一个slave对业务影响很小,因此我我的建议,新添加slave时采用冷备份slave的方式,不只备份恢复的速度最快,配置成slave也最方便,这一点和前面配置"一主一从"不同。但冷备份slave的时候须要注意几点:

  1. 能够考虑将slave1彻底shutdown再将整个datadir拷贝到新的slave2上。
  2. 建议新的slave2配置文件中的"relay-log"的值和slave1的值彻底一致,不然应该手动从slave2的relay-log.info中获取IO线程链接master时的坐标,并在slave2上使用change master to语句设置链接参数。
    方法很简单,因此不作演示了。

5.2 配置一主多从(从中有从)

此处实现的一主多从是下面这种结构:

这种结构对MySQL复制来讲,是一个很好的提高性能的方式。对于只有一个master的主从复制结构,每多一个slave,意味着master多发一部分binlog,业务稍微繁忙一点时,这种压力会加重。而这种一个主master、一个或多个辅助master的主从结构,很是有助于MySQL集群的伸缩性,对压力的适应性也很强。

除上面一主多从、从中有从的方式可提高复制性能,还有几种提高MySQL复制性能的方式:

  1. 将不一样数据库复制到不一样slave上。
  2. 能够将master上的事务表(如InnoDB)复制为slave上的非事务表(如MyISAM),这样slave上回放的速度加快,查询数据的速度在必定程度上也会提高。

回到这种主从结构,它有些不一样,master只负责传送日志给slave一、slave2和slave3,slave 2_1和slave 2_2的日志由slave2负责传送,因此slave2上也必需要开启binlog选项。此外,还必须开启一个选项--log-slave-updates让slave2可以在重放relay log时也写本身的binlog,不然slave2的binlog仅接受人为的写操做。

问:slave可否进行写操做?重放relay log的操做是否会记录到slave的binlog中?

  1. 在slave上没有开启read-only选项(只读变量)时,任何有写权限的用户均可以进行写操做,这些操做都会记录到binlog中。注意,read-only选项对具备super权限的用户以及SQL线程执行的重放写操做无效。默认这个选项是关闭的。
mysql> show variables like "read_only"; 
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| read_only     | OFF   |
+---------------+-------+
  1. 在slave上没有开启log-slave-updates和binlog选项时,重放relay log不会记录binlog。

因此若是slave2要做为某些slave的master,那么在slave2上必需要开启log-slave-updates和binlog选项。为了安全和数据一致性,在slave2上还应该启用read-only选项。

环境以下:

如下是master、slave1和slave2上配置文件内容。

# master上的配置
[mysqld]
datadir=/data
socket=/data/mysql.sock
server_id=100
sync-binlog=1
log_bin=master-bin
log-error=/data/err.log
pid-file=/data/mysqld.pid

# slave1上的配置
[mysqld]
datadir=/data
socket=/data/mysql.sock
server_id=111
relay-log=slave-bin
log-error=/data/err.log
pid-file=/data/mysqld.pid

log-slave-updates          # 新增配置
log-bin=master-slave-bin   # 新增配置
read-only=ON               # 新增配置


# slave2上的配置
[mysqld]
datadir=/data
socket=/data/mysql.sock
server_id=123
relay-log=slave-bin
log-error=/data/err.log
pid-file=/data/mysqld.pid
read-only=ON

由于slave2目前是全新的实例,因此先将slave1的基准数据备份到slave2。因为slave1自身就是slave,临时关闭一个slave对业务影响很小,因此直接采用冷备份slave的方式。

# 在slave2上执行
shell> mysqladmin -uroot -p shutdown

# 在slave1上执行:
shell> mysqladmin -uroot -p shutdown
shell> rsync -az --delete /data 192.168.100.19:/
shell> service mysqld start

冷备份时,如下几点千万注意

  1. 由于slave2是slave1的从,因此在启动MySQL前必须将备份到slave2上的和复制有关的文件都删除。包括:
    • (1).master.info。除非配置文件中指定了skip-slave-start,不然slave2将再次链接到master并做为master的slave。
    • (2).relay-log.info。由于slave1启动后会继续执行relay log中的内容(若是有未执行的),这时slave1会将这部分写入binlog并传送到slave2。
    • (3).删除relay log文件。其实不是必须删除,但建议删除。
    • (4).删除relay log index文件。
    • (5).删除DATADIR/auto.conf。这个文件必须删除,由于这里面保留了mysql server的UUID,而master和slave的UUID必须不能一致。在启动mysql的时候,若是没有这个文件会自动生成本身的UUID并保存到auto.conf中。
  2. 检查slave1上从master复制过来的专门用于复制的用户repl是否容许slave2链接。若是不容许,应该去master上修改这个用户。
  3. 由于slave1是刚开启的binlog,因此slave2链接slave1时的binlog position应该指定为4。即便slave1不是刚开启的binlog,它在重启后也会滚动binlog。

因此,在slave2上继续操做:

shell> ls /data
auto.cnf    ib_buffer_pool  ib_logfile1  performance_schema  slave-bin.000005
backuptest  ibdata1         master.info  relay-log.info      slave-bin.index
err.log     ib_logfile0     mysql        slave-bin.000004    sys            

shell> rm -f /data/{master.info,relay-log.info,auto.conf,slave-bin*}
shell> service mysqld start

最后连上slave2,启动复制线程。

shell> mysql -uroot -p
mysql> change master to
        master_host='192.168.100.150',
        master_port=3306,
        master_user='repl',
        master_password='P@ssword1!',
        master_log_file='master-slave-bin.000001',
        master_log_pos=4;
mysql> start slave;
mysql> show slave status\G

6.MySQL复制中一些经常使用操做

6.1 筛选要复制的库和表

默认状况下,slave会复制master上全部库。能够指定如下变量显式指定要复制的库、表和要忽略的库、表,也能够将其写入配置文件。

Replicate_Do_DB: 要复制的数据库
        Replicate_Ignore_DB: 不复制的数据库
         Replicate_Do_Table: 要复制的表
     Replicate_Ignore_Table: 不复制的表
    Replicate_Wild_Do_Table: 通配符方式指定要复制的表
Replicate_Wild_Ignore_Table: 通配符方式指定不复制的表

若是要指定列表,则屡次使用这些变量进行设置。

须要注意的是,尽管显式指定了要复制和忽略的库或者表,可是master仍是会将全部的binlog传给slave并写入到slave的relay log中,真正负责筛选的slave上的SQL线程

另外,若是slave上开启了binlog,SQL线程读取relay log后会将全部的事件都写入到本身的binlog中,只不过对于那些被忽略的事件只记录相关的事务号等信息,不记录事务的具体内容。因此,若是以前设置了被忽略的库或表,后来取消忽略后,它们在取消忽略之前的变化是不会再重放的,特别是基于gtid的复制会严格比较binlog中的gtid。

总之使用筛选的时候应该多多考虑是否真的要筛选,是不是永久筛选。

6.2 reset slave和reset master

reset slave会删除master.info/relay-log.info和relay log,而后新生成一个relay log。可是change master to设置的链接参数还在内存中保留着,因此此时能够直接start slave,并根据内存中的change master to链接参数复制日志。

reset slave all除了删除reset slave删除的东西,还删除内存中的change master to设置的链接信息。

reset master会删除master上全部的二进制日志,并新建一个日志。在正常运行的主从复制环境中,执行reset master极可能致使异常情况。因此建议使用purge来删除某个时间点以前的日志(应该保证只删除那些已经复制完成的日志)。

6.3 show slave hosts

若是想查看master有几个slave的信息,可使用show slave hosts。如下为某个master上的结果:

mysql> show slave hosts; 
+-----------+------+------+-----------+--------------------------------------+
| Server_id | Host | Port | Master_id | Slave_UUID                           |
+-----------+------+------+-----------+--------------------------------------+
|       111 |      | 3306 |        11 | ff7bb057-2466-11e7-8591-000c29479b32 |
|      1111 |      | 3306 |        11 | 9b119463-24d2-11e7-884e-000c29867ec2 |
+-----------+------+------+-----------+--------------------------------------+

能够看到,该show中会显示server-id、slave的主机地址和端口号、它们的master_id以及这些slave独一无二的uuid号。

其中show结果中的host显示结果是由slave上的变量report_host控制的,端口是由report_port控制的。

例如,在slave2上修改其配置文件,添加report-host项后重启MySQL服务。

[mysqld]
report_host=192.168.100.19

在slave1(前文的实验环境,slave1是slave2的master)上查看,host已经显示为新配置的项。

mysql> show slave hosts;
+-----------+----------------+------+-----------+--------------------------------------+
| Server_id | Host           | Port | Master_id | Slave_UUID                           |
+-----------+----------------+------+-----------+--------------------------------------+
|       111 | 192.168.100.19 | 3306 |        11 | ff7bb057-2466-11e7-8591-000c29479b32 |
|      1111 |                | 3306 |        11 | 9b119463-24d2-11e7-884e-000c29867ec2 |
+-----------+----------------+------+-----------+--------------------------------------+

6.4 多线程复制

在老版本中,只有一个SQL线程读取relay log并重放。重放的速度确定比IO线程写relay log的速度慢很是多,致使SQL线程很是繁忙,且实现到从库上延迟较大没错,多线程复制能够解决主从延迟问题,且使用得当的话效果很是的好(关于主从复制延迟,是生产环境下最多见的问题之一,且没有很好的办法来避免。后文稍微介绍了一点方法)

在MySQL 5.6中引入了多线程复制(multi-thread slave,简称MTS),这个多线程指的是多个SQL线程,IO线程仍是只有一个。当IO线程将master binlog写入relay log中后,一个称为"多线程协调器(multithreaded slave coordinator)"会对多个SQL线程进行调度,让它们按照必定的规则去执行relay log中的事件。

须要谨记于心的是,若是对多线程复制没有了解的很透彻,千万不要在生产环境中使用多线程复制。它的确带来了一些复制性能的提高,而且能解决主从超高延迟的问题,但随之而来的是不少的"疑难杂症",这些"疑难杂症"并不是是bug,只是须要多多了解以后才知道为什么会出现这些问题以及如何解决这些问题。稍后会简单介绍一种多线程复制问题:gaps。

经过全局变量slave-parallel-workers控制SQL线程个数,设置为非0正整数N,表示多加N个SQL线程,加上原有的共N+1个SQL线程。默认为0,表示不加任何SQL线程,即关闭多线程功能。

mysql> show variables like "%parallel%";
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| slave_parallel_workers | 0     |
+------------------------+-------+

显然,多线程只有在slave上开启才有效,由于只有slave上才有SQL线程。另外,设置了该全局变量,须要重启SQL线程才生效,不然内存中仍是只有一个SQL线程。

例如,初始时slave上的processlist以下:

设置slave_parallel_workers=2

mysql> set @@global.slave_parallel_workers=2;
mysql> stop slave sql_thread;
msyql> start slave sql_thread;
mysql> show full processlist;

可见多出了两个线程,其状态信息是"Waiting for an event from Coordinator"。

虽然是多个SQL线程,可是复制时每一个库只能使用一个线程(默认状况下,能够经过--slave-parallel-type修改并行策略),由于若是一个库可使用多个线程,多个线程并行重放relay log,可能致使数据错乱。因此应该设置线程数等于或小于要复制的库的数量,设置多了无效且浪费资源。

6.4.1 多线程复制带来的不一致问题

虽然多线程复制带来了必定的复制性能提高,但它也带来了不少问题,最严重的是一致性问题。完整的内容见官方手册。此处介绍其中一个最重要的问题。

关于多线程复制,最多见也是开启多线程复制前最须要深刻了解的问题是:因为多个SQL线程同时执行relay log中的事务,这使得slave上提交事务的顺序极可能和master binlog中记录的顺序不一致(除非指定变量slave_preserve_commit_order=1)。(注意:这里说的是事务而不是事件。由于MyISAM的binlog顺序无所谓,只要执行完了就正确,并且多线程协调器可以协调好这些任务。因此只需考虑innodb基于事务的binlog)

举个简单的例子,master上事务A先于事务B提交,到了slave上由于多SQL线程的缘由,可能事务B提交了事务A却还没提交。

是否还记得show slave status中的Exec_master_log_pos表明的意义?它表示SQL线程最近执行的事件对应的是master binlog中的哪一个位置。问题由此而来。经过show slave status,咱们看到已经执行事件对应的坐标,它前面可能还有事务没有执行。而在relay log中,事务B记录的位置是在事务A以后的(和master同样),因而事务A和事务B之间可能就存在一个孔洞(gap),这个孔洞是事务A剩余要执行的操做。

正常状况下,多线程协调器记录了一切和多线程复制相关的内容,它能识别这种孔洞(经过打低水位标记low-watermark),也能正确填充孔洞。即便是在存在孔洞的状况下执行stop slave也不会有任何问题,由于在中止SQL线程以前,它会等待先把孔洞填充完。但危险因素太多,好比忽然宕机、忽然杀掉mysqld进程等等,这些都会致使孔洞持续下去,甚至可能由于操做不当而永久丢失这部分孔洞。

那么如何避免这种问题,出现这种问题如何解决?

1.如何避免gap。

前面说了,多个SQL线程是经过协调器来调度的。默认状况下,可能会出现gap的状况,这是由于变量slave_preserve_commit_order的默认值为0。该变量指示协调器是否让每一个SQL线程执行的事务按master binlog中的顺序提交。所以,将其设置为1,而后重启SQL线程便可保证SQL线程按序提交,也就不可能会有gap的出现。

当事务B准备先于事务A提交的时候,它将一直等待。此时slave的状态将显示:

Waiting for preceding transaction to commit   # MySQL 5.7.8以后显示该状态
Waiting for its turn to commit       # MySQL 5.7.8以前显示该状态

尽管不会出现gap,但show slave statusExec_master_log_pos仍可能显示在事务A的坐标以后。

因为开启slave_preserve_commit_order涉及到很多操做,它还要求开启slave的binlog--log-bin(所以须要重启mysqld),且开启重放relay log也记录binlog的行为--log-slave-updates,此外,还必须设置多线程的并行策略--slave-parallel-type=LOGICAL_CLOCK

shell> mysqladmin -uroot -p shutdown

shell> cat /etc/my.cnf
log_bin=slave-bin
log-slave-updates
slave_parallel_workers=1
slave_parallel_type=LOGICAL_CLOCK

shell>service mysqld start

2.如何处理已经存在的gap。

方法之一,是从master上从新备份恢复到slave上,这种方法是处理不当的最后解决办法。

正常的处理方法是,使用START SLAVE [SQL_THREAD] UNTIL SQL_AFTER_MTS_GAPS;,它表示SQL线程只有先填充gaps后才能启动。实际上,它涉及了两个操做:

  • (1).填充gaps
  • (2).自动中止SQL线程(因此以后须要手动启动SQL线程)

通常来讲,在填充完gaps以后,应该先reset slave移除已经执行完的relay log,而后再去启动sql_thread。

6.4.2 多线程复制切换回单线程复制

多线程的带来的问题不止gaps一种,因此没有深刻了解多线程的状况下,千万不能在生产环境中启用它。若是想将多线程切换回单线程,能够执行以下操做:

START SLAVE UNTIL SQL_AFTER_MTS_GAPS;
SET @@GLOBAL.slave_parallel_workers = 0;
START SLAVE SQL_THREAD;

6.5 slave升级为master的大体操做

当master忽然宕机,有时候须要切换到slave,将slave提高为新的master。但对于master忽然宕机可能形成的数据丢失,主从切换是没法解决的,它只是尽量地不间断提供MySQL服务。

假如如今有主服务器M,从服务器S一、S2,S1做为未来的新的master。

  1. 在将S1提高为master以前,须要保证S1已经将relay log中的事件已经replay完成。即下面两个状态查看语句中SQL线程的状态显示为:"Slave has read all relay log; waiting for the slave I/O thread to update it"。
show slave status;
show processlist;
  1. 中止S1上的IO线程和SQL线程,而后将S1的binlog清空(要求已启用binlog)。
mysql> stop slave;
mysql> reset master;
  1. 在S2上中止IO线程和SQL线程,经过change master to修改master的指向为S1,而后再启动io线程和SQL线程。
mysql> stop slave;
mysql> change master to master_host=S1,...
mysql> start slave;
  1. 将应用程序本来指向M的请求修改成指向S1,如修改MySQL代理的目标地址。通常会经过MySQL Router、Amoeba、cobar等数据库中间件来实现。
  2. 删除S1上的master.info、relay-log.info文件,不然下次S1重启服务器会继续以slave角色运行。
  3. 未来M从新上线后,能够将其配置成S1的slave,而后修改应用程序请求的目标列表,添加上新上线的M,如将M加入到MySQL代理的读目标列表。

注意:reset master很重要,若是不是基于GTID复制且开启了log-slave-updates选项时,S1在应用relay log的时候会将其写入到本身的binlog,之后S2会复制这些日志致使重复执行的问题。

其实上面只是提供一种slave升级为Master的解决思路,在实际应用中环境可能比较复杂。例如,上面的S1是S2的master,这时S1若是没有设置为read-only,当M宕机时,能够不用中止S1,也不须要reset master等操做,受影响的操做仅仅只是S1一直没法链接M而已,但这对业务不会有多大的影响。

相信理解了前面的内容,分析主从切换的思路应该也没有多大问题。

6.6 指定不复制到slave上的语句

前面说的筛选要复制的库和表能够用于指定不复制到slave上的库和表,但却没有筛选不复制到slave的语句。

但有些特殊状况下,可能须要这种功能。例如,master上建立专门用于复制的用户repl,这种语句其实没有必要复制到slave上,甚至出于安全的考虑不该该复制到slave上。

可使用sql_log_bin变量对此进行设置,默认该变量的值为1,表示全部语句都写进binlog,从而被slave复制走。若是设置为0,则以后的语句不会写入binlog,从而实现"不复制某些语句到slave"上的功能。

例如:屏蔽建立repl用户的语句。

mysql> set sql_log_bin=0;
mysql> create user repl@'%' identified by 'P@ssword1!';
mysql> grant replication slave on *.* to repl@'%';
mysql> set sql_log_bin=1;

在使用该变量时,默认是会话范围内的变量,必定不能设置它的全局变量值,不然全部语句都将不写binlog。

6.7 主从高延迟的解决思路

slave经过IO线程获取master的binlog,并经过SQL线程来应用获取到的日志。由于各个方面的缘由,常常会出现slave的延迟(即Seconds_Behind_Master的值)很是高(动辄几天的延迟是常见的,几个小时的延迟已经算短的),使得主从状态不一致。

一个很容易理解的延迟示例是:假如master串行执行一个大事务须要30分钟,那么slave应用这个事务也大约要30分钟,从master提交的那一刻开始,slave的延迟就是30分钟,更极端一点,因为binlog的记录时间点是在事务提交时,若是这个大事务的日志量很大,好比要传输10多分钟,那么极可能延迟要达到40分钟左右。并且更严重的是,这种延迟具备滚雪球的特性,从延迟开始,很容易致使后续加重延迟。

因此,第一个优化方式是不要在mysql中使用大事务,这是mysql主从优化的第一口诀。

在回归正题,要解决slave的高延迟问题,先要知道Second_Behind_Master是如何计算延迟的:SQL线程比IO线程慢多少(其本质是NOW()减去Exec_Master_Log_Pos处设置的TIMESTAMP)。在主从网络状态良好的状况下,IO线程和master的binlog大多数时候都能保持一致(也便是IO线程没有多少延迟,除非事务很是大,致使二进制日志传输时间久,但mysql优化的一个最基本口诀就是大事务切成小事务),因此在这种理想状态下,能够认为主从延迟说的是slave上的数据状态比master要延迟多少。它的计数单位是秒。

1.从产生Binlog的master上考虑,能够在master上应用group commit的功能,并设置参数binlog_group_commit_sync_delaybinlog_group_commit_sync_no_delay_count,前者表示延迟多少秒才提交事务,后者表示要堆积多少个事务以后再提交。这样一来,事务的产生速度下降,slave的SQL线程相对就获得缓解

2.再者从slave上考虑,能够在slave上开启多线程复制(MTS)功能,让多个SQL线程同时从一个IO线程中取事务进行应用,这对于多核CPU来讲是很是有效的手段。可是前面介绍多线程复制的时候说过,没有掌握多线程复制的方方面面以前,千万不要在生产环境中使用多线程复制,要是出现gap问题,很让人崩溃。

3.最后从架构上考虑。主从延迟是由于slave跟不上master的速度,那么能够考虑对master进行节流控制,让master的性能降低,从而变相提升slave的能力。这种方法确定是没人用的,但确实是一种方法,提供了一种思路,好比slave使用性能比master更好的硬件。另外一种比较可取的方式是加多个中间slave层(也就是master->slaves->slaves),让多个中间slave层专一于复制(也可做为非业务的他用,好比用于备份)。

4.使用组复制或者Galera/PXC的多写节点,此外还能够设置相关参数,让它们对延迟自行调整。但通常都不须要调整,由于有默认设置。

还有比较细致的方面能够下降延迟,好比设置为row格式的Binlog要比statement要好,由于不须要额外执行语句,直接修改数据便可。好比master设置保证数据一致性的日志刷盘规则(sync_binlog/innodb_flush_log_at_trx_commit设置为1),而slave关闭binlog或者设置性能优先于数据一致性的binlog刷盘规则。再好比设置slave的隔离级别使得slave的锁粒度放大,不会轻易锁表(多线程复制时避免使用此方法)。还有不少方面,选择好的磁盘,设计好分库分表的结构等等,这些都是直接全局的,实在没什么必要在这里多作解释。

相关文章
相关标签/搜索