故障演练
主备架构使得数据有所冗余,当灾难来临时起码有一份完整的数据可供恢复。经常做故障演练是非常有必要的。
接下来演练一种复杂的场景:一主多从的情况下当主库宕机时,如何进行主备切换。
故障场景
假设有一组1主2备的Mysql集群,主节点
Server1
的binlog偏移量从旧到新1450、1493、1582
。此时主节点突然宕机后:
-
Server2
同步到的最新日志偏移量是1582
,即与主节点Sever1
同步。 -
Server3
由于性能或网络原因,同步稍稍延迟了一个偏移量,于是Server3
同步到的最新日志偏移量是1493
。
故障还原
我们来尝试还原故障场景。分几个步骤:
- 按照 Mysql复制指南(一) 配置一套1主2备的Mysql集群。
- 先在主库创建测试库、测试表和测试数据:
CREATE DATABASE `foo`;
USE `foo`;
CREATE TABLE `bar` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`nick_name` varchar(20) NOT NULL DEFAULT '',
`last_login_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_login_time` (`last_login_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `bar` (`nick_name`) VALUES ('ted');
随后确认一下备1、备2是否也同步创建了。
- 此时我们将备2暂停复制,用来模拟主库奔溃后
Server2
复制偏移量落后的场景。在备2执行:
STOP SLAVE;
- 我们在主库继续插入一条:
INSERT INTO `bar` (`nick_name`) VALUES ('stone');
- 关闭主库,模拟主库宕机:
$ bin/mysqladmin --socket=/tmp/mysql.sock -u root -p shutdown
故障场景还原完成
- 主库
Server1
宕机(关闭)。 - 备1
Server2
保持最新的复制同步。 - 备2
Server3
落后一条语句。
处理方案
正常思路是先想办法重新启动Server1
,如果能重新启动,那么集群又能恢复健康。但如果很不幸Server1
短时间内无法恢复,那就不得不抛弃主库,提升一台备库为新主库,同时将其他备库指向新主库并开始复制。
故障处理
在处理故障之前,我们必须先制定计划。
提升哪台备库为主库?
假设我们不知道哪台备库的复制进度比较新,我们通过show slave status
查看:
server | master_binlog | 偏移量 | 最新 |
---|---|---|---|
Server2 |
mysql-bin.000007 |
4629 |
是 |
Server3 |
mysql-bin.000007 |
4362 |
否 |
因此我们提升Server2
为主库。
提升新主库后,其他备库指向新主库时的binlog文件和偏移量应该是多少?
当Server2
成为主库后,Server3
需要指向Server2
的binlog文件。那偏移量应该设多少?
有一件事必须清楚那就是同一条语句在Server2
binlog文件里的偏移量与Server1
是完全不同的,虽然每台机器的数据都在以相同的语句同步,但彼此的binlog和偏移量都可能不一样。
最可靠的方法是找出故障时Server3
最新的复制偏移量语句,找出该语句在Server2
的binlog位置。我们用mysqlbinlog工具试一下:
先找到故障时Server3
最新的复制偏移量语句,即主库下的mysql-bin.000007
和4362
:
$ mysqlbinlog mysql-bin.000007 -vvv | less
然后找
Server2
下该语句的位置,即备1下的mysql-bin.000009
:
$ mysqlbinlog mysql-bin.000009 -vvv | less
我这里是
1354
。至此故障处理计划出炉:
- 将
Server2
提升为主库。 - 将
Server3
的复制源指向Server2
,binlog为mysql-bin.000009
、偏移量为1354
。
实施计划
将Server2
提升为主库。
更改Server2
配置文件,将read_only=1
注释掉,然后重启Server2
。
$ vim /usr/local/mysql2/etc/my.cnf
# read_only=1
重启Server2
$ bin/mysqladmin -h 127.0.0.1 -P 3307 -u root -p shutdown
$ bin/mysqld_safe --defaults-file=/usr/local/mysql2/etc/my.cnf &
为Server3
配置新的复制源并启动复制。
mysql> STOP SLAVE;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> CHANGE MASTER TO MASTER_HOST='127.0.0.1',
-> MASTER_PORT=3307,
-> MASTER_LOG_FILE='mysql-bin.000009',
-> MASTER_LOG_POS=1354;
Query OK, 0 rows affected (0.10 sec)
mysql> START SLAVE;
Query OK, 0 rows affected (0.02 sec)
mysql> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 127.0.0.1
Master_User: repl
Master_Port: 3307
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
...
此时在Server2
插入的数据已经能在Server3
下看到了,快动手试试吧!
总结
对于单主架构的Mysql集群,一旦主节点发生故障,要做非计划性的备库提升是件很麻烦的事,有大量的人工操作成本。它无法像zookeeper、elasticsearch、mongodb那样当识别到主节点(或主分片)失联后自动通过广播选举机制来选出新的主节点(或主分片)。
但辩证思考一下,开箱即用的提升特性会降低定制化能力,减小人工干预的口子。Mysql这样的复制方式反而能玩出非常多样化的拓扑结构以达到不同目的。