复制的管理与维护
监视复制状态
复制增加了监视 MySQL 的复杂程度。虽然复制事实上在主与从上都在发生,但大部分工作还是从上进行的,所以多数问题都会出在从上。
所有的复制都在工作吗?有没有从发生了错误?最慢的从落后了多远?MySQL 可以提供多数信息来回答这些问题,但要实现监视的自动化,使复制变得更加强劲,则需要管理员的工作。
查看当前二进制日志的位置和配置
在主服务器上,可以使用 SHOW MASTER STATUS 来查看当前二进制日志的位置和配置
查看当前磁盘中有哪些日志文件
mysql> SHOW MASTER LOGS;
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| mysql-bin.000220 | 425605 |
| mysql-bin.000221 | 1134128 |
| mysql-bin.000222 | 13653 |
| mysql-bin.000223 | 13634 |
+------------------+-----------+
该信息可用于判断应该给 PURGE MASTER LOGS 命令什么参数。
查看日志事件
mysql> SHOW BINLOG EVENTS;
+------------+-----+----------------+-----------+-------------+-----------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+------------+-----+----------------+-----------+-------------+-----------------------------------+
| bin.000001 | 4 | Format_desc | 10 | 124 | Server ver: 8.0.11, Binlog ver: 4 |
| bin.000001 | 124 | Previous_gtids | 10 | 155 | |
| bin.000001 | 155 | Stop | 10 | 178 | |
+------------+-----+----------------+-----------+-------------+-----------------------------------+
3 rows in set (0.00 sec)
因为我用的是两个虚拟机,而且并没有创建任何数据库,所以当前的事件很少。
查看特定位置的事件
通过以上命令可以得到某个事件的具体位置,即 Pos 字段的值。继而可以详细查看该位置的事件:
mysql> SHOW BINLOG EVENTS IN 'mysql-bin.000223' FROM 13634\G
*************************** 1. row ***************************
Log_name: mysql-bin.000223
Pos: 13634
Event_type: Query
Server_id: 1
End_log_pos: 13723
Info: use `test`; CREATE TABLE test.t(a int)
计算复制延迟
最常需要监视的就是,一个从落后了主多远。虽然 SHOW SLAVE STATUS 返回的 Seconds_Behind_Master 字段理论上可以查看从的延迟,事实上这个数字并不总是准确的,原因如下:
- 从服务器是通过把自己当前的时间戳与二进制日志事件中记录的时间戳相比较,来计算
Seconds_Behind_Master的,因此,从服务器只有进行一次查询才能进行计算。 - 如果复制进程没有运行,从服务器通常会返回
NULL - 主从之间不匹配的
max_allowed_packet设置,或不稳定的网络,这样的错误会导致复制被破坏,线程被终止,而Seconds_Behind_Master却只会显示为 0,而不会提示错误。 - 即使有些时候复制进程明明正常运行,从服务器仍然无法计算延迟,此时,它返回的要么是 0,要么是
NULL。 - 一个非常长的事务会导致计算出来的延迟产生波动。例如,某个更新数据的事务用了一个小时,然后才提交,于是在真正发生数据更新之后的一个小时,日志才被更新。当从处理该语句时,此时的报告会是自己落后主一小时,处理完成后立即变成落后 0 秒。
- 如果一个分主落后了,它下面的从都与它进度相同,这些从会跟它说我们当前零延迟,它们并不知道自己与 主 的延迟是多少。
计算延迟的最好办法 - heartbeat
这些问题的解决办法就是忽略 Seconds_Behind_Master,直接用一些可以观察和测量的手段来监视复制延迟。
最好的办法是 heartbeat record,是指在主服务器上 每秒钟更新一次的时间戳,就像心跳一样。计算延迟时,只需要在从上用 当前时间戳减掉心跳时间 就行了。
这种方法可以避免以上所有问题,还有额外的好处:可以创建一个时间戳,用来查看从的数据当前在什么时间点上。
pt-heartbeat 脚本包含于 Percona Toolkit,是复制心跳的最流行的实现。
心跳的另一好处:保存在二进制日志中的复制心跳记录可以用于多个操作,比如灾难恢复。
判断从与主是否一致
理论上讲,从服务器始终应该是主的完美副本,但实际使用中,复制中的错误会造成从上的数据变的不同步。即使没有明显的错误,从也仍然有可能不同步,原因有可能是 MySQL 的某些功能无法正确复制,或是因为 MySQL 的 bug,或是网络问题,系统崩溃,非正常关机等其他因素。
检查主从的数据是否一致应该被作为一项例行工作,如果要用复制做为备份的话,这一点尤其重要。
MySQL 没有内置的办法来判断 主从 是否同步。
pt-table-checksum
Percona Toolkit 有一个工具叫 pt-table-checksum 可解决该问题。它的主要特点是,它可以确认某个从是否与其主同步,它通过在主上运行 INSERT ... SELECT 查询来实现。
这些查询会对数据进行检验,然后把结果插入表格。这些语句在复制链上流动,在从上再次执行。之后就可以把主和从上运行的结果进行比较,看是否有区别。因为这个过程是空越复制链进行的,所以它会得到一致的结果,无需同时锁定两端服务器。
通常的做法是在主上运行,使用以下参数:
$ pt-table-checksum --replicate=test.checksum <master_host>
该命令会校验所有的表格,并把结果插入 test.checksum 表格中。
所有的从都执行完查询以后,可以用一个简单的查询来检查每个从与主的区别。
pt-table-checksum 能够发现每个从服务器,并在每个上面都运行查询,然后自动输出结果。
从主服务器重新同步
实际生产中,有时会需要处理失去同步的从服务器,也许使用检验技术找到了差异,也许发现从忽略了一个查询,或从上的数据被人为修改了。
全盘重新复制
要想修复失去同步的从服务器,通常的建议是放弃,从主服务器重新复制。如果数据不一致的从对你来说是个很严重的问题,一旦发现就应该将其停止,并尽快从生产中撤下来。然后找个备份专门为其恢复数据。
但该方法很不方便,尤其是在数据量特别大的情况下。
使用 mysqldump 进行局部复制
如果能找到哪些数据存在差异,就能比全盘复制更高效地复制。如果发现的不一致并不是特别严重,也许不用动它,直接在线把受影响的数据同步就行了。
最简单的解决办法是使用 mysqldump ,仅针对受影响的数据进行复制并重新加载,只要这期间该数据没有发生改变,这种方法就很管用。只需要在主上锁定表格,然后复制表格,等待从与主同步完成,再在从上导入该表格即可。
虽然这种方法在很多情况下很管用,但如果是特别繁忙的服务器就无法完成了。
该方法还有一个缺点,就是复制是在复制链之外进行的。我们知道,只有在复制链上从上至下地复制才是最安全的,因为可以避免发生竞争和其它意外。如果表格很大,网络带宽却很有限,复制和重新加载也会带来过高的开销。假如一个有百万行的表格中,每一千行就有一个差异的话,复制、重载整个表格将带来特别大的开销。
使用 pt-table-sync
pt-table-sync 能够高效地查找并解决表格之间的差异,还能穿越复制链进行,通过在主中执行查询来重新同步从,这样,就不会发生竞争。
它还集成了由 pt-table-checksum 创建的校验表格,以便只在有差异的表格的位置进行操作。
但它并不适用于所有的场景,它需要保证复制处于正常运行状态,以便正确地同步主从。因此,如果发生了复制错误,它就无法正常工作。
它不适用于特别大的数据,因为这会难以避免地为两端服务器造成巨大的压力。
修改主服务器
在生产中经常会需要把从指向一个新的主,原因也许是升级服务器,或是主失效了,需要把一个从提升为主,或只是重新分配吞吐量。不管什么原因,都必须告诉从它的新主是谁。
只要提前规划好,只需要在从上使用 CHANGE MASTER TO 命令。从会忽略其当前的配置以及中继日志,开始从新的主进行复制。同时还会更新 master.info 文件的内容,这样修改在重启后也会生效。
最难的部分在于,在新主上如何能找到需要的位置,这样,从就可以在原主终止的同一个逻辑位置开始了。
把一个 从 提升为 主 则有一点难度。主要有两种情况:按计划提升和非计划提升。
计划内提升
原理
把从提升为主理论上很简单:
- 停止向原主的写入
- 视需要让所有的从完成同步
- 把其中一个从配置为新主
- 把从指向新主,开始向其写入
实际步骤
在实际操作中,根据不同的拓扑可能稍有区别。
- 在当前主上停止所有写入。如果可能的话,可以考虑强制所有客户端程序退出。如果使用虚拟 IP 地址,只需要关掉这些虚拟 IP,然后杀掉所有客户端连接,从而关闭它们打开的事务。
- 在主上,视需要使用
FLUSH TABLES WITH READ LOCK停止所有写入活动,还可以用read_only选项把主设置为只读。从这个时间点开始,就应该在主上禁止一切写入,因为一旦它不再是主,向其写入数据就意味着丢失数据。不过只读的设置不会影响现有事务的提交。要想更加保险,也可以把所有打开的事务杀掉,这样会真正实现停止所有写入。 - 选择一个从来做新主,并确保它已经同步完成,一定要完成执行所有的中继日志。
- 视需要确认新主与旧主包含相同的数据。
- 在新主上执行
STOP SLAVE,翻身农奴把歌唱。 - 在新主上执行
CHANGE MASTER TO MASTER_HOST='',再执行RESET SLAVE,这样就从其原主断开了,并会忽略master.info的定义。(如果连接信息是在my.cnf指定,这一步就会失败,因此建议不要放到配置文件里) - 用
SHOW MASTER STATUS查看新主的二进制日志坐标,记下来。 - 确保所有其它从都同步完成。
- 关掉旧主。
- 在 MySQL 5.1 之后的版本上,视需要在新主上激活事件。
- 让客户端连接到新主。
- 在每个从上运行
CHANGE MASTER TO命令,指向新主,在此使用第 7 步记下来的坐标。
把从提升为主时,一定要确保其中的从专用的数据库、表格、权限都被彻底移除,同时还需要修改原有针对从的配置参数,如 innodb_flush_log_at_trx_commit 选项。同样道理,如果把主降级为从,也需要如此操作。
当然,如果最初给主和从是完全一样的配置,就不需要修改任何东西了。
计划外提升
如果主崩溃了,必须提升一个从来代替,这个过程的难度就大一些。如果只有一个从,直接用就行了,但如果有多个,还得多做几步工作。
这期间的操作有可能会丢失一些复制事件。
有可能主上发生的更新在从还没来得及进行。还可能主上执行了语句之后又回滚了,而从上没有回滚,于是实际上从的逻辑复制位置比主要超前。如果能恢复主的数据,有可能可以把丢失的语句提取出来,手工运行一遍。
以下步骤中,计算时一定要使用 Master_Log_File 和 Read_Master_Log_Pos 的值。
以下为 “一主多从” 拓扑中,提升从的步骤:
- 判断哪个从的数据最新。在每个从上查看
SHOW SLAVE STATUS的输出,看Master_Log_File和Master_Log_Pos的坐标哪个最新。 - 让所有的从把中继日志都执行完毕。
- 在新主上执行
STOP SLAVE,翻身农奴把歌唱。 - 在新主上执行
CHANGE MASTER TO MASTER_HOST='',再执行RESET SLAVE,这样就从其原主断开了,并会忽略master.info的定义。(如果连接信息是在my.cnf指定,这一步就会失败,因此建议不要放到配置文件里) - 用
SHOW MASTER STATUS查看新主的二进制日志坐标,记下来。 - 把每个从的
Master_Log_File/Read_Master_Log_Pos的坐标与新主的进行比较。 - 在 MySQL 5.1 之后的版本上,视需要在新主上激活事件。
- 让客户端连接到新主。
- 在每个从上运行
CHANGE MASTER TO命令,指向新主,在此使用第 5 步记下来的坐标。
以上步骤假设在所有从上都启用了 log_bin 和 log_slave_updates,只有启用这两个选项,才能把所有的从都恢复到同一个时间点。
定位到需要的日志位置
如果有些从与新主不在同一个位置,就必须去新主的二进制日志中,查找从所执行的最后一个事件的位置,用它来执行 CHANGE MASTER TO。
假设日志事件都有对应的渐增的数字 ID。新主刚刚获取了第 100 号事件,此时旧主崩溃了。同级还有另外两个从,即 replica2 和 replica3。replica2 刚刚获取了 99 号事件,replica3 刚获取了 98 号事件。如果把这两个从都指向新主当前的日志位置,它们会从 101 号事件开始复制,这就发生了同步偏移。然而,只要新主的二进制日志启用了
log_slave_updates,就能在其中找到 99 号和 100 号事件,就可以把这两个从带回到一致的状态。
方法一
由于服务器的重启、不同的配置、日志滚动、FLUSH LOGS 命令等因素,有些事件在不同的服务器中的字节偏移量很有可能不同。要想定位这些事件,只需在从的二进制日志或中继日志中,用 mysqlbinlog 查找最后运行的事件,然后在新主的二进制日志中用 mysqlbinlog 找到相同的查询,结果会返回该查询的字节偏移量,将其用于 CHANGE MASTER TO。
方法二
另一个办法会更快一些,就是用新主的字节偏移量减去从的字节偏移量,得到的是它们字节位置的差值。用新主当前二进制日志的位置减去这个差值,很有可能就定位到了想要找的那个查询。
范例
场景一
假设 server1 是主,server2 和 server3 是它的从,server1 现在崩溃了。

在 server2 和 server3 上用 SHOW SLAVE STATUS 查看 Master_Log_File/Read_Master_Log_Pos,结果表明 server2 完成了彻底的复制,而 server3 没有。具体来说,server2 当前的位置是与主相同的 1582,因此可以把它提升为新主。
那么,在 server3 执行 CHANGE MASTER TO 命令时,要给哪些参数呢?这里需要一点计算。server3 停止的偏移量是 1493,比 server2 的 1582 落后 89 字节。
server2 当前在自己的二进制日志中正在向位置 8167 写入,在它身上计算上一个事件的位置:
8167 - 89 = 8078
因此,理论上我们应该把 server3 指向 server2 日志的 8078 位置,为了保险,应该查看一下,以确认该位置确实有正确的事件。
假设在检查之后已经确认,server2 日志中 8078 位置的事件确实是 server3 最后一个事件。此时就可以把 server3 的主指向 server2 了:
server2> CHANGE MASTER TO MASTER_HOST="server2", MASTER_LOG_FILE="mysql-bin.000009",
MASTER_LOG_POS=8078;
场景二
假如在崩溃之前,server1 在其日志 1582 位置之后,实际上执行完了另一个事件,并已经保存进日志,又当如何呢?
因为 server2 读取并执行到了偏移量 1582,你可能就永远丢失了一个事件。然而,如果旧主的磁盘没有损坏,还是可以从其二进制日志中恢复这个事件的,可以用 mysqlbinlog 或借助一个日志服务器。
如果需要从旧主中恢复丢失的事件,建议的时机是在提升新主之后、客户端连接到新主之前。这样,就不必在每个从上执行丢失的事件了,让复制链自己去进行。
日志文件的保存
建议以一种 可靠的方式 保存主的二进制日志。
- 可以使用 SAN 或分布式复制块设备。这样,即使主彻底损坏,你仍然保留了完整的二进制日志文件。
- 可以配置一个日志服务器,然后把从指向它,让所有的从都可以有机会完整地同步到最后的位置。
把一个从提升为主时,不要把其 Server ID 改成旧主的,如果这样做了,就无法使用一个日志服务器来重演事件了,因为新主与日志中的旧主的 ID 相同,复制时事件会被忽略。因此,Server ID 始终要保持固定不变。
在 主-主 的配置中切换角色
主-主 的拓扑优点之一,就是可以轻易地切换二者的主动与被动的角色,因为它们使用了对称的配置。
切换角色时,最重要的事是:确保在任何时刻只有一个主在写入。如果二者交叉写入,容易产生冲突。因此,切换角色以后,被动服务器一定不能从对方接收任何二进制日志事件。要想保证这一点,在让被动服务器可以写入之前,需要确保其 SQL 线程与主动服务器是同步的。
以下切换角色的步骤:
- 在主动服务器上停止所有写入。
- 在主动服务器上执行
SET GLOBAL read_only=1,并在配置文件中也配置read_only,以保证重启后有效。记住,即使这样也无法阻止拥有SUPER权限的用户对数据进行修改。要想防止所有用户的修改,应该使用FLUSH TABLES WITH READ LOCK。如果不这么做,就必须杀掉所有客户端连接,以确保没有留下长时间运行的语句或未提交的事务。 - 在主动服务器上执行
SHOW MASTER STATUS,记下二进制日志的坐标。 - 使用刚刚记下的坐标,在被动服务器上执行
SELECT MASTER_POS_WAIT(),执行该命令以后,会一直保持阻塞,确保被动服务器能够读取并执行事件,一直到该坐标为止,同步完毕命令才会返回。 - 在被动服务器上执行
SET GLOBAL read_only=0,使其成为主动服务器。 - 重新配置应用程序,使其向新的主动服务器写入。