复制解决的问题


1,数据分布


MySQL复制通常不会对带宽造成很大的压力,但在5.1版本引入的基于行的复制会比传统的基于语句的复制模式的带宽压力更大。你可以随意地停止或开始复制,并在不同的地理位置来分布数据备份,例如不同的数据中心。即使在不稳定的网络环境下,远程复制也可以工作。但如果未来保持很低的复制延迟,最好有一个稳定的、低延迟连续。


2,负载均衡


通过MySQL复制可以将读操作分布到多个服务器上,实现对读密集型应用的优化,并且很方便,通过简单的代码修改就能实现基本的负载均衡。对于小规模的应用,可以简单地对机器名做硬编码或使用DNS轮询(将一个机器名指向多个IP地址)。当然也可以使用更复杂方式,例如网络负载均衡这一类的标准负载均衡解决方案,能够很好地将负载分配到不同的MySQL服务器上。LVS(Linux Virtual Server)也能够很好地工作。


3,备份


对于备份来说,复制是一项很有意义的技术补充,但复制既不是备份也不能够取代备份。


4,高可用性和故障切换


复制能够帮助应用程序避免MySQL单点失败,一个包含复制的设计良好的故障切换系统能够显著地缩短宕机时间。


5,MySQL升级测试


这种做法比较普遍,使用一个更高版本的MySQL作为备库,保证在升级全部实例前,查询能够在备库按照预期执行。


 


复制如何工作


1,在主库上把数据更改记录到二进制日志(Binary Log)中(这些记录被称为二进制日志事件)。


2,备库将主库上的日志复制到自己的中继日志(Relay Log)中。


3,备库读取中继日志中的事件,将其重放到备库数据之上。


 


下图更详细地描述了复制的细节:


《高性能MySQL》--复制笔记_数据

 


第一步是在主库上记录二进制日志。在每次准备提交事务完成数据更新前,主库将数据更新的事件记录到二进制日志中。MySQL会按事务提交的顺序而非每条语句的执行顺序来记录二进制日志。在记录二进制日志后,主库后告诉存储引擎可以提交事务了。


下一步,备库将主库的二进制日志复制到其本地的中继日志中。首先,备库会启动一个工作线程,称为I/O线程、I/O线程跟主库建立一个普通的客户端连接,然后在主库上启动一个特殊的二进制转储(binlog dump)线程(该线程没有对应的SQL命令),这个二进制转储线程会读取主库上二进制日志的事件。它不会对事件进行轮询。如果该线程追赶上了主库,它将进入睡眠状态,知道主库发送信号量通知其有新的事件产生时才会被唤醒,备库I/O线程会将接收到的事件记录到中继日志中。


备库的SQL线程执行最后一步,该线程从中继日志中读取事件并在备库执行,从而实现备库数据的更新。当SQL线程追赶上I/O线程是,中继日志通常已经在系统缓存中,所以中继日志的开销很低。SQL线程执行的事件也可以通过配置选项来决定是否写入其自己二进制日志中。


 


配置复制


1,复制账号。


在主库和备库都创建该账号:



mysql> GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.*


-> TO repl@'192.168.0.%' IDENTIFIED BY 'p4ssword';


 


2,配置主库和备库。


 



配置master


接下来对master进行配置,包括打开二进制日志,指定唯一的servr ID。例如,在配置文件加入如下值:


[mysqld]


log-bin=mysql-bin


server-id=10


重启master,运行SHOW MASTER STATUS,输出如下:


《高性能MySQL》--复制笔记_mysql_02

 



配置slave


Slave的配置与master类似,你同样需要重启slave的MySQL。如下:


log_bin           = mysql-bin


server_id         = 2


relay_log         = mysql-relay-bin


log_slave_updates = 1


read_only         = 1


server_id是必须的,而且唯一。slave没有必要开启二进制日志,但是在一些情况下,必须设置,例如,如果slave为其它slave的master,必须设置bin_log。在这里,我们开启了二进制日志,而且显示的命名(默认名称为hostname,但是,如果hostname改变则会出现问题)。


relay_log指定中继日志的位置和命名。log_slave_updates表示允许备库将其重放的事件也记录到自身的二进制日志中。


有时候开启了slave的二进制日志,却没有设置log_slave_updates,可能会碰到一些奇怪的现象。例如,当配置错误时可能会导致备库数据被修改。如果可能的话,最好使用使用read_only配置选项,该选项会阻止没有任何特权权限的线程修改数据。但read_only通常不是很实用,特别是那些需要在slave上创建表的应用。


 


3,启动复制


接下来就是让slave连接master,并开始重做master二进制日志中的事件。你不应该用配置文件进行该操作,而应该使用CHANGE MASTER TO语句,该语句可以完全取代对配置文件的修改,而且它可以为slave指定不同的master,而不需要停止服务器。如下:



mysql> CHANGE MASTER TO MASTER_HOST='server1',


MASTER_USER='repl',


 MASTER_PASSWORD='p4ssword',


MASTER_LOG_FILE='mysql-bin.000001',


MASTER_LOG_POS=0;


 



MASTER_LOG_POS的值为0,因为它是日志的开始位置。然后,你可以用SHOW SLAVE STATUS语句查看slave的设置是否正确:


 


mysql> SHOW SLAVE STATUS\G


 


*************************** 1. row ***************************


             Slave_IO_State:


                Master_Host: server1


                Master_User: repl


                Master_Port: 3306


              Connect_Retry: 60


            Master_Log_File: mysql-bin.000001


        Read_Master_Log_Pos: 4


             Relay_Log_File: mysql-relay-bin.000001


              Relay_Log_Pos: 4


      Relay_Master_Log_File: mysql-bin.000001


           Slave_IO_Running: No


          Slave_SQL_Running: No


                             ...omitted...


      Seconds_Behind_Master: NULL


 


Slave_IO_State, Slave_IO_Running, 和Slave_SQL_Running表明slave还没有开始复制过程。日志的位置为4而不是0,这是因为0只是日志文件的开始位置,并不是日志位置。实际上,MySQL知道的第一个事件的位置是4。


 


为了开始复制,你可以运行:


 


mysql> START SLAVE;


 


运行SHOW SLAVE STATUS查看输出结果:


 


mysql> SHOW SLAVE STATUS\G


*************************** 1. row ***************************


             Slave_IO_State: Waiting for master to send event


                Master_Host: server1


                Master_User: repl


                Master_Port: 3306


              Connect_Retry: 60


            Master_Log_File: mysql-bin.000001


        Read_Master_Log_Pos: 164


             Relay_Log_File: mysql-relay-bin.000001


              Relay_Log_Pos: 164


      Relay_Master_Log_File: mysql-bin.000001


           Slave_IO_Running: Yes


          Slave_SQL_Running: Yes


                             ...omitted...


      Seconds_Behind_Master: 0


 


注意,slave的I/O和SQL线程都已经开始运行,而且Seconds_Behind_Master不再是NULL。日志的位置增加了,意味着一些事件被获取并执行了。如果你在master上进行修改,你可以在slave上看到各种日志文件的位置的变化,同样,你也可以看到数据库中数据的变化。


 


你可查看master和slave上线程的状态。在master上,你可以看到slave的I/O线程创建的连接:


 


mysql> show processlist \G


*************************** 1. row ***************************


     Id: 1


   User: root


   Host: localhost:2096


     db: test


Command: Query


   Time: 0


 State: NULL


   Info: show processlist


*************************** 2. row ***************************


     Id: 2


   User: repl


   Host: localhost:2144


     db: NULL


Command: Binlog Dump


   Time: 1838


 State: Has sent all binlog to slave; waiting for binlog to be updated


   Info: NULL


 


2 rows in set (0.00 sec)


 


 行2为处理slave的I/O线程的连接。


在slave上运行该语句:


mysql> show processlist \G


*************************** 1. row ***************************


     Id: 1


   User: system user


   Host:


     db: NULL


Command: Connect


   Time: 2291


 State: Waiting for master to send event


   Info: NULL


*************************** 2. row ***************************


     Id: 2


   User: system user


   Host:


     db: NULL


Command: Connect


   Time: 1852


 State: Has read all relay log; waiting for the slave I/O thread to update it


   Info: NULL


*************************** 3. row ***************************


     Id: 5


   User: root


   Host: localhost:2152


     db: test


Command: Query


   Time: 0


 State: NULL


   Info: show processlist


3 rows in set (0.00 sec)


 


行1为I/O线程状态,行2为SQL线程状态。


 


从另外一个服务器开始复制


前面的设置都是假定主备库均为刚刚安装好且都是默认的数据,也就是说两台服务器上数据相同,并且知道当前主库的二进制日志。这不是典型的案例。大多数情况下有一个已经运行了一段时间的主库,然后用一台新安装的备库与之同步,此时这台备库还没有数据。


需要有三个条件来让主库好备库保持同步:


1,在某个时间点的主库的数据快照。


2,主库当前的二进制日志文件,和获得数据快照时在该二进制日志文件中的偏移量,我们把这两个值称为日志文件坐标(log file coordinates)。通过这两个值可以确定二进制日志的位置。可以通过SHOW MASTER STATUS命令来获取这些值。


3,从快照时间到现在的二进制日志。


 


下面是一些从别的服务器克隆备库的方法:


1,使用冷备份


最基本的方法是关闭主库,把数据复制到备库。重启主库后,会使用一个新的二进制日志文件,我们在备库通过执行CHANGE MASTER TO 指向这个文件的起始处。这个缺点很明显:在复制数据时需要关闭主库。


2,使用热备份


如果仅适用了MyISAM表,可以在主库运行时使用msyqlhotcopy或rsync来复制数据。


3,使用mysqldump


如果只包含InnoDB表,那么可以使用以下命令来转储主库数据并将其加载到备库,然后设置相应的二进制日志坐标:


mysqldump --single-transaction  --all-databases --master-data=1 --host=server1 | mysql --host=server


选项 --single-transaction 使得转储的数据为事务开始前的数据。如果使用的是非事务型表,可以使用--lock--all-tables选项来获得所有表的一致性转储。


4,使用快照或者备份


只要知道对于的二进制日志坐标,就可以使用主库的快照或者备份来初始化备库(如果使用备份,需要确保从备份的时间点开始的主库二进制日志都要存在)。只需要把备份或快照恢复到备库,然后使用CHANGE MASTER TO制定二进制日志的坐标。


5,使用xtrabackup


此工具能够在备份时不阻塞服务器的操作,因此可以在不影响主库的情况下设置备库。可以通过克隆主库或另一个已存在的备库的方式来建立备库。如果是从主库获得备份,可以从xtrabackup_binlog_pos_innodb文件中获得复制开始的位置。如果是从另外的备库获得备份,可以从xtrabackup_slave_info文件中获得复制开始的位置。


6,使用另外的备库


可以使用任何一种提及的克隆或者拷贝技术来从任意一台备库上将数据克隆到另外一台服务器。但是如果使用的是mysqldummp,--master-data选项就不会起作用。此外,不能使用SHOW MASTER STATUS来获得主库的二进制日志坐标,而是在获得快照时使用SHOW SLAVE STATUS来获取备库在主库上的执行位置。使用另外的备库进行数据数据克隆最大的缺点是,如果这台备库的数据已经和主库不同步,克隆得到的就是脏数据。


 


注意:不要使用LOAD DATA FROM MASTER 或者LOAD TABLE MASTER 这些命令过时、缓慢,并且非常危险,并且只适用于MyISAM存储引擎。


 


推荐的复制配置


在主库上二进制日志最重要的选项是sync_binlog:


sync_binglog=1


如果开启该选项,MySQL每次在提交事务前会将二进制日志同步到磁盘上,保证在服务器奔溃时不会丢失事件。如果禁止该选项,服务器会少一些工作,但二进制日志文件可能在服务器崩溃时损坏或丢失信息。在一个不需要作为主库的备库上,该选项带来了不必要的开销。它只适用于二进制日志,而非中继日志。


如果无法忍受服务器崩溃导致表损坏,推荐使用InnoDB。在表损坏无关紧要时,MySIAM是可以接受的,但在一次备库服务器崩溃重启后,MyISAM表可能已经处于不一致状态。一种可能是语句没有完全应用到一个或多个表上。那么即使修复了表,数据也可能是不一致的。


如果使用InnoDB,强烈建议设置如下选项:


innodb_flush_logs_at_trx_commit    #Flush every log write


innodb_support_xa=1                       #MySQL 5.0 and newer only


innodb_safe_binlog                           #MySQL 4.1 only,roughly equivalent to innodb_support_xa


 


这些是MySQL 5.0及最新版本中的默认配置,推荐明确指定二进制日志的名字,以保证二进制日志名在所有服务器上是一致的,避免因为服务器名的变化导致的日志文件名变化。当在服务器间转移文件、克隆新的备库、转储备份或者其他场景,可能会导致很多问题,为了避免这些问题,需要给log_bin选项制定一个参数。可以随意地给一个绝对路径,但必须明确地制定基本的命名。


在备库上,推荐开启如下配置选项,为中继日志制定绝对路径:


relay_log=/path/to/logs/relay_bin


skip_slave_start


read_only


 


通过设置relay_log可以避免中继日志文件基于机器名来命名,防止之前提到的可能在主库发生的问题。制定绝对路径可以避免多个MySQL版本中存在的BUG,这些BUG可能会导致中继日志在一个意料外的位置创建。skip_slave_start选项能够阻止备库在崩溃后自动启动复制。


read_only选项可以阻止大部分用户更改非临时表,除了复制SQL线程和其他超级权限的用户之外,这也是要尽量避免给正常账号授予超级权限的原因之一。


即使开启了所有建议的选项,备库仍然可能在崩溃后被中断,因为master.info和中继日志文件都不是崩溃安全的。默认情况下甚至不会刷新到磁盘,知道MySQL5.5版本才有选项控制这种行为。如果正在使用MySQL5.5并且不介意额外的fsync()导致的性能开销,最好设置以下选项:


sync_master_info=1


sync_relay_log=1


sync_relay_log_info=1


如果备库与主库的延迟很大,备库的I/O线程可能会写很多中继日志文件,SQL线程在重放完一个中继日志中的事件会尽快将其删除(通过relay_log_purger选项来控制)。但如果延迟非常严重,I/O线程可能会把整个磁盘撑满。解决办法是配置relay_log_space_limit变量。如果所有中继日志的大小之和超过这个值,I/O线程会停止,等待SQL线程释放盘空间。


 


基于语句的复制


在MySQL 5.0及之前的版本中只支持基于语句的复制(也称为逻辑复制),这在数据库领域是很少见的。语句语句的复制模式下,主库会记录那些造成数据更改的查询。当备库读取并重放这些事件时,实际上只是把主库上执行过的SQL再执行一遍。


理论上将,简单地记录和执行这些语句,能够让主库保持同步。另一个好处是二进制日志里的事件更加紧凑,所以相对而言,基于语句的模式不糊使用太多带宽。一条更新好几兆数据的语句在二进制日志里可能只占几十个字节。另外mysqlbinlog工具是使用基于语句的日志的最佳工具。


但事实上基于语句的方式可能并不如其看起来那么便利。因为主库上的数据更新除了执行的语句外,可能还依赖于其他因素。例如,同一条SQL在主库和备库上执行的事件可能稍微或很不相同,因此在传输的二进制日志中,除了查询语句外,还包括了一些元数据信息,如当前的时间戳。即便如此,还存在着一些无法被正确复制的SQL。例如,使用CURRENT_USER()函数的语句。存储过程和触发器在使用基于语句的复制模式时也可能存在问题。


另外一个问题是更新必须是串行的,这需要更多的锁。另外不是所有的存储引擎都支持这种复制模式。尽管这些存储引擎是包括在MySQL5.5及之前版本中发行的。


 


基于行的复制


MySQL 5.1开始支持基于行的复制,这种方式会将实际数据记录在二进制日志中,跟其他数据库的实现比较相像。它有其自身的一些优点和缺点。最大的好处是可以正确的复制每一行。一些语句可以被更加有效地复制。


由于无须重放更新主库数据的查询,使用基于行的复制模式能够更高地复制数据。重放一些查询的代价可能会很高。例如,下面有一个查将数据从一个大表中汇总到小表:



mysql> INSERT INTO summary_table(col1, col2, sum_col3)


SELECT col1, col2, sum(col3)


FROM enormous_table


GROUP BY col1, col2;


想象一下,如果表enormous_tabled 列col1和col2有三种组合,这个查询可能在源表上扫描多次,但最终只在目标表上产生三行数据。但使用基于行的复制方式,在备库上开销会小很多。这种情况下,基于行的复制模式更加高效。


但在另一方面,下面这条语句使用基于语句的复制方式代价会小很多:


mysql> UPDATE enormous_table SET col1=0;


由于这条语句做了全表更新,使用基于行的复制开销会很大,因为每一行的数据都会被记录到二进制日志中,这使得二进制日志事件非常庞大。并且会给主库上记录日志和复制增加额外的负载,更慢的日志记录则会降低并发度。


由于没有那种模式对所有情况都是完美的,MySQL能够在这两种复制模式间动态切换,默认情况下使用的是基于语句的复制方式,但如果发现语句无法正确地复制,就切换到基于行的复制模式。还可以根据需要来设置会话级别的变量binlog_format,控制二进制日志格式。


对于基于行的复制模式,很难进行时间点恢复,但这并非不可能。


 


基于行或基于语句:那种最优


理论上基于行的复制模式整体上更优,并且在实际应用中也适用于大多数场景。


 


基于语句的复制模式的优点:


当主备的模式不同时,逻辑复制能够在多种情况下工作。例如,在主备上的表的定义不同但数据类型相兼容、列的顺序不同等情况下。这样就很容易先在备库上修改schema,然后将其提升为主库,减少停机时间。基于语句复制方式一般允许更灵活的操作。基于语句的方式执行复制的过程基本上就是执行SQL语句。这意味着所有在服务器上发生的变更都以一种容易理解的方式运行。这样出问题时可以很好地去定位。


 


基于语句的复制模式的缺点:


很多情况下通过基于语句的模式无法正确复制,几乎每一个安装的备库都会至少碰到一次。事实上对于存储过程,触发器以及其他的一些语句的复制在5.0和5.1的一系列版本中存在大量的BUG。这些语句的复制的方式已经被修改了很多次,以使其更好地工作。简单地说:如果正在使用触发器或者存储过程,就不要使用基于语句的复制模式,除非能够清楚地确定不会碰到复制问题。


 


基于行的复制模式的优点:


几乎没有基于行的复制模式无法处理的场景。对于所有的SQL构造、触发器、存储过程等都能正确执行。只是当你试图做一些诸如在备库修改表的schema这样的事情时才可能导致复制失败。这种方式同样可能减少锁的使用,因为它并不要求这种强串行化是可重复的。基于行的复制模式会记录数据变更,因此在二进制日志中记录的都是实际上在主库上发生了变化的数据。你不需要查看一条语句去猜测它到底修改了哪些数据。在某种程度上,该模式能够更加清楚地知道服务器上发生了哪些更改,并且有一个更高的数据变更记录。另外在一些情况下基于行的二进制日志还会记录发生改变之前的数据,因此这可能有利于某些数据恢复。在很多情况下基于行的二进制日志还会记录发生改变之前的数据,因此这可能有利于某些数据恢复。在很多情况下,由于无需像基于语句的复制那样需要为查询建立执行计划并执行查询,因此基于行的复制占用更少的CPU。最后,在某些情况下,基于行的复制能够帮助更快地找到并解决数据不一致的情况。举个例子,如果是使用基于语句的复制模式,在备库更新一个不存在的记录时不会失败,但在基于行的复制模式下则会报错并停止复制。


 


基于行的复制模式的缺点:


由于语句并没有在日志里记录,因此无法判断执行了哪些SQL,除了需要知道行的变化外,这在很多情况下也很重要(这可能在未来的MySQL版本中被修复)。使用一种完全不同的方式在备库进行数据变更,而不是执行SQL。事实上,执行基于行的变化的过程就像一个黑盒子,你无法知道服务器正在做什么。因此当出现问题时,可能很难找到问题所在。例如,若备库使用一个效率低下的方法去寻找行记录并更新,你无法观察到这一点。如果有多层的复制服务器,并且所有的都被配置成基于行的复制模式,当会话级别的变量@@binlog_format被设置成STATEMENT时,所指向的语句在源服务器上呗记录为基于语句的模式,但第一层的备库可能将其记录成行模式,并传递给其他层的备库。也就是说你期望的基于语句的日志在复制拓扑中将会被切换到基于行的模式。基于行的日志无法处理诸如备库修改表的scema这样的情况,而基于语句的日志可以在某些情况下,例如找不到要修改的行时,基于行的复制可能会导致复制停止,而基于语句的复制则不会。这也可以认为是基于行的复制的一个优点。该行为可以通过slave_exec_mode来进行配置。