你的位置:首页 > 数据库

[数据库]浅析MySQL复制


MySQL的复制是基于binlog来实现的。

 

流程如下

涉及到三个线程,主库的DUMP线程,从库的IO线程和SQL线程。

1. 主库将所有操作都记录到binlog中。当复制开启时,主库的DUMP线程根据从库IO线程的请求将binlog中的内容发送到从库。

2. 从库的IO线程接受到主库DUMP线程发送的binlog事件后,将其写到本地的relay-log。

3. 从库的SQL线程重放relay-log中的事件。

实际上,在MySQL 4.0之前,复制只有两个线程,master和slave端各一个。在Slave端,该线程同时负责接收主库发来的binlog事件,也负责事件的重放,所以没有使用relay-log,这样容易导致,当binlog事件的重放速度较慢时,会影响binlog事件的接受。

 

复制的搭建

基本步骤如下:

1. 配置主库和从库

2. 创建复制的账号

3. 创建主库一致性快照

4. 根据主库的快照,建立从库

5. 开启复制

 

详细步骤如下

1. 配置主库和从库

主库

开启binlog并设置server-id

[mysqld]log-bin=mysql-binserver-id=1

在一组复制结构中,每个服务器必须配置一个唯一的server-id。该值的有效范围为1~232-1。

如果server-id设置为0的话,则MySQL会自动将它更改为1。此时,对复制没有影响。

如果server-id没有显式设置的话,则MySQL同样会将它设置为1,但是从连接的时候,IO线程会报错

Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'Misconfigured master - server_id was not set'

 

从库

设置server-id

[mysqld]server-id=2

在从服务器上,可以选择不开启binlog。当开启了binlog后,如果想把重放的时间同样也记录到binlog中,可将log_slave_updates参数设置为1。

 

设置完毕后,重启数据库

 

2. 创建复制账号

主库上执行

mysql> CREATE USER 'repl'@'%.mydomain.com' IDENTIFIED BY 'slavepass';mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%.mydomain.com';

 

3. 创建主库一致性快照

在这里,我用mysqldump来创建数据库快照

# mysqldump --master-data=2  -R --single-transaction -A > 3306_20160815.sql

在生成的备份文件中,我们可以得到在对数据库执行一致性快照时主的状态,包括二进制文件的名称及位置

# head -30 3306_20160815.sql

...-- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000016', MASTER_LOG_POS=120;...

 

4. 根据主库的快照,建立从库

# mysql < 3306_20160815.sql

 

5. 开启复制

根据第3步获取到的主库状态执行CHANGE MASTER TO命令

mysql> CHANGE MASTER TO     MASTER_HOST='master_host_name',     MASTER_USER='replication_user_name',     MASTER_PASSWORD='replication_password',     MASTER_LOG_FILE='recorded_log_file_name',     MASTER_LOG_POS=recorded_log_position;

在执行CHANGE MASTER TO命令后,从库并不会连接到主库,而只是将这些信息写入到从库数据目录下的master.info和relay-log.info中。如果没有显式指定MASTER_LOG_FILE,则默认为空,因为此时还没有和主库建立连接,并不知道主库binlog的文件名称,只有等到和主库建立连接时才能知道。如果MASTER_LOG_POS没有显式指定,则默认为4,即忽略binlog的头4个字节,从第一个事件开始读取。

 

开启复制功能

mysql> start slave;

 

查看复制的状态

mysql> show slave status\G

重点关注以下两个变量,如果为YES,则代表复制搭建成功。

...Slave_IO_Running: YesSlave_SQL_Running: Yes...

 

注:以上搭建场景是基于主库上已经有一定的数据。如果在全新的环境中搭建,实际上会更简单。

只需获取主库的状态信息即可

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

 

MySQL复制的格式

MySQL的复制有三种格式:

Statement-based replication(SBR)

基于语句的复制,即master上执行的SQL语句原封不动的在slave上重放。该复制格式在MySQL 3.23即出现了。

优点:

1. 节省binlog的空间。

2. 可用于审核,毕竟所有的DML语句都是直接记录在binlog中。

缺点:

1. 很多函数在主从上执行的结果并不一致

LOAD_FILE(),UUID(), UUID_SHORT(),USER(),FOUND_ROWS(),SYSDATE(),GET_LOCK(),IS_FREE_LOCK(),IS_USED_LOCK(),MASTER_POS_WAIT(),RAND(),RELEASE_LOCK(),SLEEP(),VERSION()

2. DELETE和UPDATE操作,带了LIMIT子句,却没有带ORDER BY,可能导致主从执行的结果并不一致。

3. 相对于基于行的复制,master上执行INSERT ... SELECT操作需要更多的行锁。

4. 自定义函数(UDF)必须确保执行的结果是确定的。

 

Row-based replication(RBR)

基于行的复制,MySQL 5.1引入的,相对于SBR,它记录的是DML操作涉及到的行。

优点:

1. 安全。master上所有的变更都能复制到slave上。

2. 在执行以下操作时,只需更少的行锁。

    INSERT ... SELECT

    带有AUTO_INCREMENT列的INSERT操作

    UPDATE和DELETE中,WHERE条件没有用上索引。

缺点:

1. 会产生大量的日志。

譬如一张表有1w条记录,如果我不带任何条件执行delete操作,则在基于statement的复制中,在binlog中只会记录delete from table这一条记录,但是在基于row的复制中,则会记录1w笔记录,每笔记录类似于delete from table where ..。

这会带来以下问题

 1> 如果利用binlog进行恢复,会需要更长的时间.

 2> 在写数据到binlog中时,因为数据量大,会导致binlog的锁定时间较长,影响数据库的并发。

 3> 较大的日志会对磁盘IO和网络IO产生较大的压力。

 4> 增大slave的延迟。

2. 不会对二进制日志进行校验。

3. 不推荐基于库级别的复制

    包括--replicate-do-db, --replicate-ignore-db, --replicate-rewrite-db

 

MIXED

MIXED是上述两者的的结合,会根据执行的语句和涉及的存储引擎自动在这两种模式间切换。默认情况下,采用的是基于语句的复制模式,在遇到unsafe statements时,会切换为基于行的复制模式。

关于切换的条件,可参考官方文档:http://dev.mysql.com/doc/refman/5.7/en/binary-log-mixed.html

 

在MySQL 5.7.7之前,默认的是基于语句的复制模式,从MySQL 5.7.7开始,默认基于行的复制模式。

 

复制中涉及的文件

relay-log

relay-log保存着从库IO线程从主库读取到的binlog事件。和binlog格式一样,可通过mysqlbinlog解析其中的内容。

可通过配置relay_log和relay_log_index参数设置relay-log和relay-log.index文件的名称。

 

relay-log在如下情况下会发生切换:

1. slave IO线程启动的时候。

2. 执行FLUSH LOGS操作的时候。

3. 达到参数max_relay_log_size设置的大小,默认为0,即以max_binlog_size的值作为max_relay_log_size的大小。

slave SQL线程在重放完一个relay-log文件中的所有事件后,会自动删除relay-log文件(由relay_log_purge参数控制),所以没有显式删除relay-log的命令。

 

master.info

该文件保存了主库的主机名,端口,复制账号的用户名和密码,以及从库接受主库binlog事件的位置信息,通过这些位置信息,从库的IO线程知道下次从哪里获取主库的binlog事件。

该位置信息对应于show slave status\G中的Master_Log_File和Read_Master_Log_Pos。

如下所示:

# cat master.info

23mysql-bin.000014120192.168.244.10replrepl330660001800.0000cad449f2-5d4f-11e6-b353-000c29c64704864000

 

relay-log.info

该文件记录从库的重放信息,这样即便数据库发生重启,SQL线程也知道该从哪里开始重放。

该位置信息对应于show slave status\G中的Relay_Master_Log_File和Exec_Master_Log_Pos。

# cat relay-log.info

7./mysqld-relay-bin.000024283mysql-bin.0000141200011

 

Crash-Safe Replication

slave每次接受binlog或者应用relay-log时,都要修改master.info或relay-log.info的信息并同步到磁盘中,这会导致大量的磁盘操作,所以,一般都是采用异步方式来对这两个文件进行更改。虽然每次都会修改这些文件,但是持久化到磁盘却留给操作系统处理。在内存与磁盘中的信息不一致时,如果此时操作系统宕机,内存中的信息将无法及时同步到磁盘中。操作系统恢复后,master.info和relay-log.info的数据依然是旧的,所以会从已经执行的部分重新执行。

MySQL 5.6为了改善这个问题,提供了两个参数,可将master.info和relay-log.info中的内容保存在表中而不是磁盘文件中。

master-info-repository

可设置为TABLE或FILE,如果使用FILE,则复制的位置信息依旧保存在master.info中,如果设置为TABLE,则信息报保存在mysql.slave_master_info中,默认为FILE。

relay-log-info-repository

可设置为TABLE或FILE,如果使用FILE,则应用relay-log的信息会保存在relay-log.info中,如果设置为TABLE,则保存在mysql.slave_relay_log_info中,默认为FILE。

 

通过将上述两个变量设置为TABLE,可实现“Crash-Safe Replication”,上述的相关操作都会放到一个事务中进行。

 

总结

1. 启用复制功能并不会增加服务器太多的开销,主要是开启binlog带来的开销,包括binlog文件的追加写操作开销,以及系统调用fsync带来的开销。

2. MySQL的复制功能具有向后兼容性,因为较新版本的MySQL的binlog中会进入新的事件类型。所以,可以将较新版本的MySQL作为从库。

3. MySQL复制是异步的。

4. MySQL 5.6开始新增延迟复制功能,由master_delay参数来控制。

 

参考

1. http://dev.mysql.com/doc/refman/5.7/en/replication-configuration.html

2. MariaDB原理与实现