“这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战”
1. 延时从库
- 理念:控制 SQL 线程延时,数据已经写入 relaylog 中了,SQL 线程“慢点”执行
- 经验:一般企业建议 3-6 个小时,具体看公司运维人员对于故障的反应时间
mysql> stop slave;
mysql> change master to master_delay=300;
mysql> start slave;
mysql> show slave status\G
SQL_Delay: 300 -- 延时时间
SQL_Remaining_Delay: NULL -- 最近一个事务执行还剩多少秒
mysql> create database test2; -- 主库
mysql> show slave status\G
SQL_Delay: 300
SQL_Remaining_Delay: 287 -- 剩余时间变了
mysql> drop database test2; -- 主库
mysql> stop slave sql_thread;
此时主库执行了删库操作,发现第一步我们的操作应该是停掉 SQL 线程,执行 stop slave sql_thread;。然后我们应该模拟 SQL 线程执行数据。
1.2 延时从库处理逻辑故障
1.2.1 延时从库的恢复思路
-
监控到数据库逻辑故障
-
停从库 SQL 线程,记录已经回放的位置点(截取日志起点)
- stop slave sql_thread;
- Relay_Log_File: VM-0-3-centos-relay-bin.000002
- Relay_Log_Pos: 644
-
截取 relaylog
-
起点:show slave status\G
- Relay_Log_File、Relay_Log_Pos
-
终点:drop 之前的位置点
- show relaylog events in 'VM-0-3-centos-relay-bin.000002';
- 进行截取
-
-
模拟 SQL 线程回放日志
- 从库 source
-
恢复业务:
- 情况一:就一个库,从库替代主库
- 情况二:有多个库,从库导出故障库,还原到主库中
1.2.2 故障演练
- 主库:
mysql> create database delay charset utf8mb4;
mysql> use delay;
mysql> create table t1(id int);
mysql> insert into t1 values (1),(2),(3);
mysql> drop database delay;
然后我们检测到 relay 这个数据库没有了。我们去主库查看一下,数据库确实没有了。
- 从库
- 停止从库 SQL 线程,获取 relay 位置点
mysql> stop slave sql_thread; -- 停止从库 SQL 线程
mysql> show slave status\G -- 获取起点位置
Relay_Log_File: VM-0-3-centos-relay-bin.000003
Relay_Log_Pos: 472
- 找到 relay 的截取终点
mysql> show relaylog events in 'VM-0-3-centos-relay-bin.000003';
+--------------------------------+------+----------------+-----------+-------------+---------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+--------------------------------+------+----------------+-----------+-------------+---------------------------------------+
| VM-0-3-centos-relay-bin.000003 | 4 | Format_desc | 8 | 123 | Server ver: 5.7.26-log, Binlog ver: 4 |
| VM-0-3-centos-relay-bin.000003 | 123 | Previous_gtids | 8 | 154 | |
| VM-0-3-centos-relay-bin.000003 | 154 | Rotate | 7 | 0 | mysql-bin.000001;pos=5949 |
| VM-0-3-centos-relay-bin.000003 | 201 | Format_desc | 7 | 0 | Server ver: 5.7.26-log, Binlog ver: 4 |
| VM-0-3-centos-relay-bin.000003 | 320 | Anonymous_Gtid | 7 | 6014 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| VM-0-3-centos-relay-bin.000003 | 385 | Query | 7 | 6101 | drop database test3 |
| VM-0-3-centos-relay-bin.000003 | 472 | Anonymous_Gtid | 7 | 6166 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| VM-0-3-centos-relay-bin.000003 | 537 | Query | 7 | 6279 | create database delay charset utf8mb4 |
| VM-0-3-centos-relay-bin.000003 | 650 | Anonymous_Gtid | 7 | 6344 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| VM-0-3-centos-relay-bin.000003 | 715 | Query | 7 | 6443 | use `delay`; create table t1(id int) |
| VM-0-3-centos-relay-bin.000003 | 814 | Anonymous_Gtid | 7 | 6508 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| VM-0-3-centos-relay-bin.000003 | 879 | Query | 7 | 6581 | BEGIN |
| VM-0-3-centos-relay-bin.000003 | 952 | Table_map | 7 | 6627 | table_id: 146 (delay.t1) |
| VM-0-3-centos-relay-bin.000003 | 998 | Write_rows | 7 | 6677 | table_id: 146 flags: STMT_END_F |
| VM-0-3-centos-relay-bin.000003 | 1048 | Xid | 7 | 6708 | COMMIT /* xid=632 */ |
| VM-0-3-centos-relay-bin.000003 | 1079 | Anonymous_Gtid | 7 | 6773 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| VM-0-3-centos-relay-bin.000003 | 1144 | Query | 7 | 6868 | drop database delay |
+--------------------------------+------+----------------+-----------+-------------+---------------------------------------+
17 rows in set (0.00 sec)
左边 Pos 表示的是 relaylog 的起点,右边 End_log_pos 表示的是 binlog 的起点。我们看 relaylog 就可以(看左边就可以)。
在上图中,1144 是 drop 的起点,也是上一条语句的终点,所以我们选择这个 Pos。
- 截取 relay
# mysqlbinlog --start-position=472 --stop-position=1144 VM-0-3-centos-relay-bin.000003 > /tmp/relay.sql
截取完了一定要看看 drop 操作没在文件中
- 恢复 relay 到从库
mysql> set sql_log_bin=0;
mysql> source /tmp/relay.sql
mysql> set sql_log_bin=1;
接下来我们就按照上述我们说的,该怎么恢复主库,就怎么恢复主库了。
我选择备份从库数据,恢复到主库:
# mysqldump -uroot -S /data/3308/mysql.sock -B delay > /tmp/delay_bak.sql
主库:
mysql> set sql_log_bin=0;
mysql> source /tmp/delay_bak.sql
mysql> set sql_log_bin=1;
mysql> show databases;
mysql> use delay;
mysql> select * from t1;
从库重启一下同步:
mysql> stop slave;
mysql> start slave;
2. 过滤复制
2.1 快速恢复测试环境
- 从库
mysql> drop database delay;
mysql> stop slave;
mysql> reset slave all;
- 主库:
mysql> reset master;
- 从库:
mysql> CHANGE MASTER TO
MASTER_HOST='172.21.0.3',
MASTER_USER='repl',
MASTER_PASSWORD='',
MASTER_PORT=3307,
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154,
MASTER_CONNECT_RETRY=10;
mysql> start slave;
2.2 过滤复制应用
主库方面过滤,DUMP_T 线程不传送某个库的更新信息(一般不使用这种方式)
主库:
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 154 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
- Binlog_Do_DB:白名单,表示要复制的数据库
- Binlog_Ignore_DB:黑名单,表示要忽略的数据库
一般情况下,配置其中一个即可。
- 从库
mysql> show slave status\G
Replicate_Do_DB: -- 白名单(库级别)
Replicate_Ignore_DB: -- 黑名单(库级别)
Replicate_Do_Table: -- 白名单(表级别)
Replicate_Ignore_Table: -- 黑名单(表级别)
Replicate_Wild_Do_Table: -- 白名单(表级别,可匹配多个表)
Replicate_Wild_Ignore_Table: -- 黑名单(表级别,可匹配多个表)
vim /data/3308/my.cnf
replicate_do_db=repl
systemctl restart mysqld3308
此时,我们查看一下主库信息:
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 479 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
再查看一下从库信息:
mysql> show slave status\G
Master_Log_File: mysql-bin.000001
Read_Master_Log_Pos: 479
Replicate_Do_DB: repl
可以看到主从正常同步,且只同步 repl 数据库。
此时我们在主库创建一个其他数据库:create database aa;。我们再去从库查看一下:show databases;,发现没有 aa 这个数据库。
此时我们在主库创建一个其他数据库:create database repl;。我们再去从库查看一下:show databases;,发现有了 repl 这个数据库。
3. GTID
3.1 GTID 介绍
GTID(Global Transaction ID)是对于一个已提交事务的唯一编号,并且是一个全局(主从复制)唯一的编号。
它的官方定义如下:
GTID = source_id :transaction_id
3.2 GTID 环境准备
准备三台机器:(一主二从)
-
创建 mysql 用户
useradd -s /sbin/nologin mysql
-
下载 mysql 5.7.26 安装包并解压,解压到 /home/mysql/mysql-5.7.26 目录下
-
创建数据目录
mkdir /data/mysql/data -p
-
修改配置文件(三台机器分别修改,一主二从)
- 主库
cat > /etc/my.cnf <<EOF
[mysqld]
user=mysql
basedir=/home/mysql/mysql-5.7.26
datadir=/data/mysql/data
socket=/tmp/mysql.sock
server_id=51
port=3306
secure-file-priv=/tmp
log_bin=/data/binlog/mysql-bin
binlog_format=row
gtid-mode=on
enforce-gtid-consistency=true
log-slave-updates=1
[mysql]
prompt=db-master [\\d]>
EOF
- 从库 1
cat > /etc/my.cnf <<EOF
[mysqld]
user=mysql
basedir=/home/mysql/mysql-5.7.26
datadir=/data/mysql/data
socket=/tmp/mysql.sock
server_id=52
port=3306
secure-file-priv=/tmp
log_bin=/data/binlog/mysql-bin
binlog_format=row
gtid-mode=on
enforce-gtid-consistency=true
log-slave-updates=1
[mysql]
prompt=db-slave1 [\\d]>
EOF
- 从库 2
cat > /etc/my.cnf <<EOF
[mysqld]
user=mysql
basedir=/home/mysql/mysql-5.7.26
datadir=/data/mysql/data
socket=/tmp/mysql.sock
server_id=53
port=3306
secure-file-priv=/tmp
log_bin=/data/binlog/mysql-bin
binlog_format=row
gtid-mode=on
enforce-gtid-consistency=true
log-slave-updates=1
[mysql]
prompt=db-slave2 [\\d]>
EOF
-
创建 binlog 目录
mkdir /data/binlog
-
初始化 MySQL,无密码登录
mysqld --initialize-insecure --user=mysql --basedir=/home/mysql/mysql-5.7.26 --datadir=/data/mysql/data
-
修改文件权限
chown -R mysql.mysql /home/mysql/mysql-5.7.26chown -R mysql.mysql /data/
-
启动 MySQL
/home/mysql/mysql-5.7.26/support-files/mysql.server start
3.3 构建主从
-
主库:
grant replication slave on *.* to repl@'10.20.1.5%' identified by 'wys';
-
从库:
CHANGE MASTER TO
MASTER_HOST='10.20.2.92',
MASTER_USER='repl',
MASTER_PASSWORD='wys',
MASTER_PORT=3306,
MASTER_AUTO_POSITION=1;
查看从库状态:
db-slave1 [(none)]> start slave;
db-slave1 [(none)]>show slave status\G
db-slave2 [(none)]> start slave;
db-slave2 [(none)]>show slave status\G
3.4 GTID 核心参数
# 开启 GTID,否则就是普通的复制架构
gtid-mode=on
# 强制 GTID 一致性
enforce-gtid-consistency=true
# 强制从库刷新 binlog 日志
log-slave-updates=1
# 读取 relaylog 最后一个事务的 GTID
MASTER_AUTO_POSITION=1
3.5 总结
区别(同普通主从复制)
- 在主从复制环境中,主库发生过的事务,在全局都是由唯一 GTID 记录的,更方便 Failover
- 额外功能参数(3 个)
change master to的时候不再需要 binlog 文件名和 position 号,使用MASTER_AUTO_POSITION=1。- mysqldump 备份时,默认会将备份中包含的事务操作,以以下方式:
SET @@GLOBAL.GTID_PURGED='b6148b54-6684-11eb-946f-c81f66c506f5:1';- 这行命令是告诉从库,我的备份中已经有以上事务,你就不用运行了,直接从下一个 GTID 开始请求 binlog 就行。
细节:
- 构建主从时,要先把主库备份,然后再去做主从构建
mysqldump --set-gtid-purged=auto,默认是 auto,如果我们设置了off,就没有SET @@GLOBAL.GTID_PURGED='b6148b54-6684-11eb-946f-c81f66c506f5:1'这行信息了。不应该添加 off。
讨论:
为什么设置了 OFF 就构建不了主从了?
如果我们设置了 OFF,那么就没有 SET @@GLOBAL.GTID_PURGED='b6148b54-6684-11eb-946f-c81f66c506f5:1-11' 这行数据了。
假设我们有 11 个事务(1-11),此时备份文件已经有了这 11 个事务了。假设把这行注释掉了,那么从库就不知道已经有了这 11 个事务了。而我们又设置了 MASTER_AUTO_POSITION=1 了,那我们意思是从主库的第一个事务开始请求,但是我们已经有了前 11 个事务了,然后就重复了,就无法构建成功。因此,不能加 OFF,默认设置 AUTO。
4. 半同步
解决主从复制数据一致性问题
ACK
从库 relay 落地,IO 线程会返回一个 ACK,主库有个 ACK_receiver 线程。接收到了信号,主库事务才会提交,如果一直 ACK 没有收到,超过 10 秒钟会切换为异步复制。