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-bin server-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 statusG
重点关注以下两个变量,如果为YES,则代表复制搭建成功。
...
Slave_IO_Running: Yes
Slave_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 statusG中的Master_Log_File和Read_Master_Log_Pos。
如下所示:
# cat master.info
23 mysql-bin.000014 120 192.168.244.10 repl repl 3306 60 0 0 1800.000 0 cad449f2-5d4f-11e6-b353-000c29c64704 86400 0
relay-log.info
该文件记录从库的重放信息,这样即便数据库发生重启,SQL线程也知道该从哪里开始重放。
该位置信息对应于show slave statusG中的Relay_Master_Log_File和Exec_Master_Log_Pos。
# cat relay-log.info
7 ./mysqld-relay-bin.000024 283 mysql-bin.000014 120 0 0 1 1
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原理与实现