如何提升 MySQL RBR 的健壮性

MySQL 复制场景中,从库承担着很多的读请求的压力,特殊时刻还能临时充当主库,而复制是否可靠是从库能否承担这些角色的前提。但是,想必大家都会或多或少遇到过复制出错的问题吧?主从不一致?还在一个劲地跳错误点吗?重建复制是不是很痛苦?

看完下面的文章,教你正确配置MySQL复制。




一、先来一个常规演示:

搭建一个基于行的一主一从复制

==============================================

master:192.168.237.21:3306,slave:192.168.237.21:3309

server-id:237213306、237213309

首先确认从库上是否已有连到其他主库的复制关系,有的话需要stop slave,MySQL 5.6只能指定一个主库来做从库(官方直到5.7才引入了多源复制)。

我们这里是个新搭的库,没啥搞头,下面直接开始备份主库了。

[root@237_21 mysql3309]# innobackupex --defaults-file=/usr/local/mysql3306/my.cnf --user=root --password='123456' --host=192.168.237.21 --port=3306 --no-timestamp /tmp/innobackup_all/0129
[root@237_21 mysql3309]# innobackupex --apply-log --defaults-file=/usr/local/mysql3306/my.cnf --user=root --password='123456' --host=192.168.237.21 --port=3306 /tmp/innobackup_all/0129
关闭从库,清空数据目录,导入主库的备份数据
[root@237_21 mysql3309]# innobackupex --copy-back --user=root --password='123456' --socket=/tmp/mysql3309.sock --defaults-file=/usr/local/mysql3309/my.cnf /tmp/innobackup_all/0129
修改数据目录权限,并再次启动从库。
查看数据目录下的xtrabackup_info文件,确认:filename 'bin.000092', position 191, 采用位置点的方式在从库上建立复制关系

mysql> change master to master_host='192.168.237.21',master_port=3306,master_user='rpl',master_password='123456',master_log_file='bin.000092',master_log_pos=191;
Query OK, 0 rows affected, 2 warnings (0.05 sec)

mysql> start slave;
Query OK, 0 rows affected (0.01 sec)

show slave status \G 确认IO线程和SQL线程均正常。

==============================================

一般大家从官网或者其他一些资料上看到的复制搭建都是像上面这样,很简单,但是还不能满足实际可用、可靠的要求。

我们看下以下两种异常情况。




二、从库存在写入的情况

我们以如下的一张表testdb.a为例

mysql> show create table a \G
*************************** 1. row ***************************
       Table: a
Create Table: CREATE TABLE `a` (
  `id` int NOT NULL AUTO_INCREMENT,
  `score` int(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

(以下操作均在auto_commit=0情况下演示)

先在主库上分两次插入数据(1,90)、(2,65)和(3,80)、(4,85),然后再在从库上插入记录(5,70)并commit

再在主库插入(5,70)并commit

此时从库复制状态出错

1)show slave status收集到的主要信息如下:

--IO线程读取到的主库binlog位置点信息
Master_Log_File: bin.000093
Read_Master_Log_Pos: 2506

--SQL线程执行到的relay log对应主库的binlog位置点信息
Relay_Master_Log_File: bin.000093
Exec_Master_Log_Pos: 2062

--IO、SQL线程的运行状态
Slave_IO_Running: Yes
Slave_SQL_Running: No

--最近一次IO\SQL线程错误代码
Last_Errno: 1062
Last_IO_Errno: 0
Last_SQL_Errno: 1062

--最近一次IO\SQL线程错误详情
Last_Error: Could not execute Write_rows event on table testdb.a; Duplicate entry '5' for key 'PRIMARY', Error_code: 1062; handler error HA_ERR_FOUND_DUPP_KEY; the event's master log bin.000093, end_log_pos 2475
Last_IO_Error:
Last_SQL_Error: Could not execute Write_rows event on table testdb.a; Duplicate entry '5' for key 'PRIMARY', Error_code: 1062; handler error HA_ERR_FOUND_DUPP_KEY; the event's master log bin.000093, end_log_pos 2475

对应的错误日志如下:

2018-01-29 16:39:20 9471 [ERROR] Slave SQL: Could not execute Write_rows event on table testdb.a; Duplicate entry '5' for key 'PRIMARY', Error_code: 1062; handler error HA_ERR_FOUND_DUPP_KEY; the event's master log bin.000093, end_log_pos 2475, Error_code: 1062
2018-01-29 16:39:20 9471 [Warning] Slave: Duplicate entry '5' for key 'PRIMARY' Error_code: 1062
2018-01-29 16:39:20 9471 [ERROR] Error running query, slave SQL thread aborted. Fix the problem, and restart the slave SQL thread with "SLAVE START". We stopped at log 'bin.000093' position 2062

大体意思就是,主库传来的binlog中2475~2062的event无法在从库上回放,主键冲突,Error_code:1062


2)show binlog events

+------------+------+----------------+-----------+-------------+-----------------------------------------------------------------------------------------------------------------------------------+
| Log_name   | Pos  | Event_type     | Server_id | End_log_pos | Info                                                                                                                              |
+------------+------+----------------+-----------+-------------+-----------------------------------------------------------------------------------------------------------------------------------+

| bin.000093 | 1655 | Gtid           | 237213306 |        1703 | SET @@SESSION.GTID_NEXT= '04bbee11-946c-11e7-a006-000c29827dab:83812'                                                             |
| bin.000093 | 1703 | Query          | 237213306 |        1821 | use `testdb`; DROP TABLE `a` /* generated by server */                                                                            |
| bin.000093 | 1821 | Gtid           | 237213306 |        1869 | SET @@SESSION.GTID_NEXT= '04bbee11-946c-11e7-a006-000c29827dab:83813'                                                             |
| bin.000093 | 1869 | Query          | 237213306 |        2062 | use `testdb`; CREATE TABLE a (id int primary key auto_increment,score int(10) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 |
| bin.000093 | 2062 | Gtid           | 237213306 |        2110 | SET @@SESSION.GTID_NEXT= '04bbee11-946c-11e7-a006-000c29827dab:83814'                                                             |
| bin.000093 | 2110 | Query          | 237213306 |        2184 | BEGIN                                                                                                                             |
| bin.000093 | 2184 | Table_map      | 237213306 |        2231 | table_id: 72 (testdb.a)                                                                                                           |
| bin.000093 | 2231 | Write_rows     | 237213306 |        2284 | table_id: 72 flags: STMT_END_F                                                                                                    |
| bin.000093 | 2284 | Table_map      | 237213306 |        2331 | table_id: 72 (testdb.a)                                                                                                           |
| bin.000093 | 2331 | Write_rows     | 237213306 |        2384 | table_id: 72 flags: STMT_END_F                                                                                                    |
| bin.000093 | 2384 | Table_map      | 237213306 |        2431 | table_id: 72 (testdb.a)                                                                                                           |
| bin.000093 | 2431 | Write_rows     | 237213306 |        2475 | table_id: 72 flags: STMT_END_F                                                                                                    |
| bin.000093 | 2475 | Xid            | 237213306 |        2506 | COMMIT /* xid=76 */                                                                

这个binlog该怎么看呢?我们提取一个正常insert操作,在row模式的binlog中记录如下:

| bin.000093 | 1167 | Gtid           | 237213306 |        1215 | SET @@SESSION.GTID_NEXT= '04bbee11-946c-11e7-a006-000c29827dab:83810'                                                             |
| bin.000093 | 1215 | Query          | 237213306 |        1289 | BEGIN                                                                                                                             |
| bin.000093 | 1289 | Table_map      | 237213306 |        1336 | table_id: 70 (testdb.a)                                                                                                           |
| bin.000093 | 1336 | Write_rows     | 237213306 |        1380 | table_id: 70 flags: STMT_END_F                                                                                                    |
| bin.000093 | 1380 | Xid            | 237213306 |        1411 | COMMIT /* xid=52 */                                                                

可以看到,一条封装在事务中的DML语句包含:Gtid、Query、Table_map、Write_rows、Xid一共5个event

据此推断前一份binlog中:前两条write_rows是插入前4条测试数据,第三条write_rows是插入(5,70)

那么,我们在不了解insert情况下,如何判断具体这个事务中的哪条SQL导致复制出错呢?

?????


同样,当从库执行过建库、表语句,当主库有同名建库、表操作由binlog传递过来时,同样会产生冲突。

或者,当从库删除了某个库、某个表、某条记录,而主库欲对相关记录进行操作时,也会报错。

所以,为了避免上述情况发生,MySQL官方引入了如下参数:

从库:read_only=1:限制从库的写入操作,但是super权限用户仍然不受控。(5.7引入super_read_only,将super用户也纳入管控)



那么是不是加入了这个参数,复制就高枕无忧了?

我们先修复上面的问题,根据:Could not execute Write_rows event on table testdb.a; Duplicate entry '5' for key 'PRIMARY' 给到的提示,删除从库上有主键冲突的 id=5 的记录,再start slave;(直接跳过引起冲突的event当然也可以,看实际取舍)

此时,主库上的5条记录正常回放过来了,主从数据一致。




三、从库意外宕机

当主库插入完1条记录(6,95)后,从库宕机

mysql> select * from a;
+----+-------+
| id | score |
+----+-------+
|  1 |    90 |
|  2 |    65 |
|  3 |    80 |
|  4 |    85 |
|  5 |    70 |
|  6 |    95 |
+----+-------+
6 rows in set (0.00 sec)

重启后发现,复制状态又不正常了。还是一样的1062主键冲突。


1)SQL线程复制可靠性探讨:

事实上,在回放过程中,SQL线程只做两件事,回放relay log中的event,以及刷新relay-info.log,最后由文件系统定期fsync该文件。

第一个例子的问题就出在第一件回放event的事上,而现在这个例子就是刷新relay-info.log的问题。

我们引入第二个参数:relay_log_info_repository:默认FILE,记录的是SQL线程执行到的binlog位置信息

SQL线程每执行到一个位置点(回放完一个event)后,更新一次文件:relay-info.log(为提高性能不实时刷盘,而是由参数sync_relay_log_info=10000 决定每更新1万次该文件,才最终fsync一次,除此之外的其他触发点是??)

正是由于这样一种fsync的机制,引发了一个问题:当从机意外宕机,relay-info.log中记录的将不是最新的(从库实际已回放到了最新的relay log)。重启之后,SQL线程将读取relay-info.log文件,从中找到执行的起始位置点,那么就会从宕机前最近一次fsync所记录到的位置点开始继续回放relay log,这部分relay log将重复执行(如果存在一些按主键进行insert的操作的话,就容易引起1062的错误)。而像这样一种写数据库的操作(insert ...)和写文件的操作(fsync到磁盘上relay-info.log)并不能保证(最终)一致性。即使每回放一个event就fsync一次relay-info.log,仍然存在宕机重启后重复执行最近一个event的情况。

relay_log_info_repository设置为TABLE,将上述对event的回放和对文件的更新操作变为两次写数据库操作,在数据库内利用事务的特性来实现一致性还是比较容易的。

注:官方5.5版本的MySQL无此参数,上述的数据不一致问题无法解决。


2)IO线程复制可靠性探讨:

IO线程的工作也是两件事:在master.info中写接收到的位置点信息,再写relay log(同样有参数sync_master_info控制刷盘的频率),最后由文件系统负责fsync

两个都是写文件操作,特别是写relay log无法转化为数据库内的操作,这就导致无法像SQL线程一样处理该问题了。

所以我们引入第三个参数:relay-log-recovery=1:宕机重启之后,回放过的relay log将会被删除。将以SQL线程执行到的位置作为IO线程拉日志的起点(按前面讲述的方法设置过后,SQL线程是可靠的,所以这些参数都是要配合使用的)

注:以SQL线程执行到的位置点为准,意味会丢弃部分IO线程已拉取而SQL线程尚未回放的binlog,导致重复拉取部分数据,影响初段的复制效率。此外,如果主库设置了过小的expire_logs_days的话,也不能完全忽略从库拉不到主库上早期binlog的情况。




经过以上配置,MySQL5.6的复制将会是很可靠的,如果再有出错问题,一般可以判断为拥有super权限的DBA们在从库上误加数据导致。而5.7引入的新参数 super_read_only 将这最后一道门也堵上了。


关于 master_info_repository 导致的复制性能问题:

在从库上开启并行复制的情况下

slave_parallel_type=LOGICAL_CLOCK

slave_parallel_workers=4

务必将 master_info_repository 设置为 TABLE,可以有较大的性能提升。



备份方法可参考我的另外一篇博文:

MySQL备份最流行的两把工具 --- Innobackupex 和 mydumper

复制架构中,主从参数的详注:

Replication Master Options and Variables

Replication Slave Options and Variables

5.6.25 Bugs Fixed:Bug #72885, Bug #19390463, Bug #69848, Bug #20124342

关于主从复制延时,可以参考我的这篇博文:

如何正确衡量主从延迟时间

关于master_info_repository:FILE 引起的性能问题,可以查看facebook提交的bug

MySQL 5.6 Bug #70342

Performance issues and fixes -- MySQL 5.6 Semi-Synchrnous Replication


猜你喜欢

转载自blog.csdn.net/leonpenn/article/details/79195056
今日推荐