MySQL实战宝典 高可用架构篇 15 MySQL 复制:最简单

2021-06-21  本文已影响0人  逢春枯木

业务上线除了表和索引的结构设计之外,你还要做好高可用的设计,因为在真实的生产环境下,如果发生物理硬件故障,没有搭建高可用架构,会导致业务完全不可用。这在海量并发访问的互联网业务中完全无法不敢想象。接下来就来学习MySQL高可用架构中最基础、最核心的内容:MySQL复制(Replication)

MySQL复制架构

数据库复制本质上就是数据同步。MySQL数据库是基于二进制日志(Binary log)进行数据增量同步,而二进制日志记录了所有对于MySQL数据库的修改操作。

在默认ROW格式二进制日志中,一条SQL操作影响的记录会被全部记录下来,比如一条SQL语句更新了3条记录,在二进制日志中会记录被修改的这3条记录的前像(before image)和后像(after image)。

对于INSERT或DELETE操作,则会记录这条被插入或删除记录的所有列的信息,我们来看一个例子:

mysql> DELETE FROM orders_test WHERE o_orderdate = '1997-12-31';
Query OK, 2482 rows affected (8.72 sec)

上面这条SQL执行的删除操作,一共删除了有2382行记录。

可以使用命令SHOW BINARY LOGS查看当前binlog列表

mysql> SHOW BINARY LOGS;
+---------------+------------+-----------+
| Log_name      | File_size  | Encrypted |
+---------------+------------+-----------+
| binlog.000001 |    3118706 | No        |
| binlog.000002 |       6331 | No        |
| binlog.000003 |       1912 | No        |
| binlog.000004 |        156 | No        |
| binlog.000005 |        179 | No        |
| binlog.000006 | 1073823128 | No        |
| binlog.000007 | 1073951976 | No        |
| binlog.000008 | 1074373961 | No        |
| binlog.000009 |  585638385 | No        |
| binlog.000010 |        179 | No        |
| binlog.000011 |       2203 | No        |
| binlog.000012 |        179 | No        |
| binlog.000013 |        179 | No        |
| binlog.000014 | 1228526038 | No        |
| binlog.000015 |     254189 | No        |
+---------------+------------+-----------+
15 rows in set (0.00 sec)

可以使用命令SHOW MASTER STATUS查看最新的binlog

mysql> SHOW MASTER STATUS;
+---------------+----------+--------------+------------------+-------------------+
| File          | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+---------------+----------+--------------+------------------+-------------------+
| binlog.000015 |   254189 |              |                  |                   |
+---------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

可以在mysql命令行下使用命令SHOW BINLOG EVENTS查看某个二进制日志文件的内容,比如上述删除操作发生在二进制日志文件binlog.000015中,可以看到:

mysql> SHOW BINLOG EVENTS IN 'binlog.000015';
+---------------+--------+----------------+-----------+-------------+--------------------------------------+
| Log_name      | Pos    | Event_type     | Server_id | End_log_pos | Info                                 |
+---------------+--------+----------------+-----------+-------------+--------------------------------------+
| binlog.000015 |      4 | Format_desc    |         1 |         125 | Server ver: 8.0.23, Binlog ver: 4    |
| binlog.000015 |    125 | Previous_gtids |         1 |         156 |                                      |
| binlog.000015 |    156 | Anonymous_Gtid |         1 |         236 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| binlog.000015 |    236 | Query          |         1 |         311 | BEGIN                                |
| binlog.000015 |    311 | Table_map      |         1 |         390 | table_id: 100 (tpch.orders_test)     |
| binlog.000015 |    390 | Delete_rows    |         1 |        8594 | table_id: 100                        |
| binlog.000015 |   8594 | Delete_rows    |         1 |       16786 | table_id: 100                        |
| binlog.000015 |  16786 | Delete_rows    |         1 |       24947 | table_id: 100                        |
| binlog.000015 |  24947 | Delete_rows    |         1 |       33105 | table_id: 100                        |
| binlog.000015 |  33105 | Delete_rows    |         1 |       41264 | table_id: 100                        |
| binlog.000015 |  41264 | Delete_rows    |         1 |       49425 | table_id: 100                        |
| binlog.000015 |  49425 | Delete_rows    |         1 |       57622 | table_id: 100                        |
| binlog.000015 |  57622 | Delete_rows    |         1 |       65756 | table_id: 100                        |
| binlog.000015 |  65756 | Delete_rows    |         1 |       73929 | table_id: 100                        |
| binlog.000015 |  73929 | Delete_rows    |         1 |       82105 | table_id: 100                        |
| binlog.000015 |  82105 | Delete_rows    |         1 |       90228 | table_id: 100                        |
| binlog.000015 |  90228 | Delete_rows    |         1 |       98389 | table_id: 100                        |
| binlog.000015 |  98389 | Delete_rows    |         1 |      106591 | table_id: 100                        |
| binlog.000015 | 106591 | Delete_rows    |         1 |      114786 | table_id: 100                        |
| binlog.000015 | 114786 | Delete_rows    |         1 |      122928 | table_id: 100                        |
| binlog.000015 | 122928 | Delete_rows    |         1 |      131035 | table_id: 100                        |
| binlog.000015 | 131035 | Delete_rows    |         1 |      139137 | table_id: 100                        |
| binlog.000015 | 139137 | Delete_rows    |         1 |      147349 | table_id: 100                        |
| binlog.000015 | 147349 | Delete_rows    |         1 |      155551 | table_id: 100                        |
| binlog.000015 | 155551 | Delete_rows    |         1 |      163730 | table_id: 100                        |
| binlog.000015 | 163730 | Delete_rows    |         1 |      171921 | table_id: 100                        |
| binlog.000015 | 171921 | Delete_rows    |         1 |      180123 | table_id: 100                        |
| binlog.000015 | 180123 | Delete_rows    |         1 |      188241 | table_id: 100                        |
| binlog.000015 | 188241 | Delete_rows    |         1 |      196383 | table_id: 100                        |
| binlog.000015 | 196383 | Delete_rows    |         1 |      204591 | table_id: 100                        |
| binlog.000015 | 204591 | Delete_rows    |         1 |      212742 | table_id: 100                        |
| binlog.000015 | 212742 | Delete_rows    |         1 |      220924 | table_id: 100                        |
| binlog.000015 | 220924 | Delete_rows    |         1 |      229098 | table_id: 100                        |
| binlog.000015 | 229098 | Delete_rows    |         1 |      237241 | table_id: 100                        |
| binlog.000015 | 237241 | Delete_rows    |         1 |      245423 | table_id: 100                        |
| binlog.000015 | 245423 | Delete_rows    |         1 |      253538 | table_id: 100                        |
| binlog.000015 | 253538 | Delete_rows    |         1 |      254158 | table_id: 100 flags: STMT_END_F      |
| binlog.000015 | 254158 | Xid            |         1 |      254189 | COMMIT /* xid=61 */                  |
+---------------+--------+----------------+-----------+-------------+--------------------------------------+
38 rows in set (0.00 sec)

可以通过MySQL数据库自带的命令mysqlbinlog解析二进制日志,观察到更为详细的每条记录的信息,比如:

root@3164e75abacd:/# mysqlbinlog -vv /var/lib/mysql/binlog.000015 | more
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#210621 14:45:29 server id 1  end_log_pos 125 CRC32 0x530b9461  Start: binlog v 4, server v 8.0.23 created 210621 14:45:29
# Warning: this binlog is either in use or was not closed properly.
### DELETE FROM `tpch`.`orders_test`
### WHERE
###   @1=4901 /* INT meta=0 nullable=0 is_null=0 */
###   @2=315652 /* INT meta=0 nullable=0 is_null=0 */
###   @3='O' /* STRING(4) meta=65028 nullable=0 is_null=0 */
###   @4=240305.85 /* DECIMAL(15,2) meta=3842 nullable=0 is_null=0 */
###   @5='1997:12:31' /* DATE meta=0 nullable=0 is_null=0 */
###   @6='4-NOT SPECIFIED' /* STRING(60) meta=65084 nullable=0 is_null=0 */
###   @7='Clerk#000003920' /* STRING(60) meta=65084 nullable=0 is_null=0 */
###   @8=0 /* INT meta=0 nullable=0 is_null=0 */
###   @9='inal dependencies cajole furiously. carefully express accounts na' /* VARSTRING(316) meta=316 nullable=0 is_null=0 */
### DELETE FROM `tpch`.`orders_test`
### WHERE
###   @1=20228 /* INT meta=0 nullable=0 is_null=0 */
###   @2=339929 /* INT meta=0 nullable=0 is_null=0 */
###   @3='O' /* STRING(4) meta=65028 nullable=0 is_null=0 */
###   @4=47099.47 /* DECIMAL(15,2) meta=3842 nullable=0 is_null=0 */
###   @5='1997:12:31' /* DATE meta=0 nullable=0 is_null=0 */
###   @6='2-HIGH' /* STRING(60) meta=65084 nullable=0 is_null=0 */
###   @7='Clerk#000000207' /* STRING(60) meta=65084 nullable=0 is_null=0 */
###   @8=0 /* INT meta=0 nullable=0 is_null=0 */
###   @9='ar requests! blithely even foxes haggle carefully furiously sil' /* VARSTRING(316) meta=316 nullable=0 is_null=0 */

你可以通过二进制日志记录看到被删除记录的完整信息,还有每个列的属性,比如列的类型,是否允许为 NULL 值等。

如果是 UPDATE 操作,二进制日志中还记录了被修改记录完整的前项和后项,比如:

### UPDATE `tpch`.`orders_test`
### WHERE
###   @1=23993606
###   @2=487481
###   @3='O'
###   @4=326921.95
###   @5='1997:12:30'
###   @6='3-MEDIUM'
###   @7='Clerk#000001474'
###   @8=0
###   @9='ng to the furiously even pinto beans sle'
### SET
###   @1=23993606
###   @2=487481
###   @3='O'
###   @4=326921.95
###   @5='2021:06:22'
###   @6='3-MEDIUM'
###   @7='Clerk#000001474'
###   @8=0
###   @9='ng to the furiously even pinto beans sle'

在有二进制日志的基础上,MySQL数据库可以通过数据复制技术实现数据同步了。而数据复制的本质就是把一台MySQL数据库上的变更同步到另一台MySQL数据库中,如下图:

MySQL 数据库的复制架构

在MySQL复制中,一台数据库服务器的角色是Master,剩下的服务器角色均为Slave:

得益于二进制日志,MySQL的复制相比其他关系型数据库,如Oracle、PostgreDB等,非常灵活,用户可以根据自己的需要购机所需的复制拓扑结构,比如:

复制拓扑结构

在上图中,Slave1、Slave2、Slave3 都是 Master 的从服务器,而 Slave11 是 Slave1 的从服务器,Slave1 服务器既是 Master 的从机,又是 Slave11 的主机,所以 Slave1 是个级联的从机。同理,Slave3 也是台级联的从机。

在了解完复制的基本概念后,我们继续看如何配置 MySQL 的复制吧。

MySQL复制配置

搭建 MySQL 复制实现非常简单,基本步骤如下:

  1. 创建复制所需的账号和权限;

  2. 从 Master 服务器拷贝一份数据,可以使用逻辑备份工具 mysqldump、mysqlpump,或物理备份工具 Clone Plugin;

  3. 通过命令 CHANGE MASTER TO 搭建复制关系;

  4. 通过命令 SHOW SLAVE STATUS 观察复制状态。

虽然 MySQL 复制原理和实施非常简单,但在配置时却容易出错,请你务必在配置文件中设置如下配置:

gtid_mode = on
enforce_gtid_consistency = 1
binlog_gtid_simple_recovery = 1
relay_log_recovery = ON
master_info_repository = TABLE 
relay_log_info_repository = TABLE

上述设置都是用于保证 crash safe,即无论 Master 还是 Slave 宕机,当它们恢复后,连上主机后,主从数据依然一致,不会产生任何不一致的问题。

请确认上述参数都已配置,否则任何的不一致都不是 MySQL 的问题,而是你使用 MySQL 的错误姿势所致。

了解完复制的配置后,我们接下来看一下 MySQL 支持的复制类型。

MySQL复制类型及应用选项

MySQL 复制可以分为以下几种类型:

MySQL 复制类型
异步复制

在异步复制(async replication)中,Master不用关心Slave是否接收到二进制日志,所以Master与Slave是分别独自工作的两台服务器,数据最终会通过二进制日志达到一致。

异步复制的性能最好,因为它对数据库本身几乎没有任何开销,除非主从延迟非常大,Dump Thread需要读取大量二进制日志文件。

如果业务对于数据一致性要求不高,当发送故障时,能容忍数据的丢失,甚至大量的丢失,推荐用异步复制,这样性能最好(比如像微博这样的业务,虽然他对性能的要求极高,但对于数据丢失,通常可以容忍)。但往往核心业务系统最关心的就是数据安全,比如监控业务、告警系统。

半同步复制

半同步复制要求Mater事务提交过程中,至少有N个Slave接收到二进制日志,这样就能保证当Master发生宕机,至少有N台Slave数据库中过的数据是完整的。

半同步复制并不是MySQL内置的功能,而需要安装半同步插件,并启用半同步复制功能,设置N个Slave接收二进制日志成功,比如:

plugin-load="rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so"
rpl-semi-sync-master-enabled = 1
rpl-semi-sync-slave-enabled = 1
rpl_semi_sync_master_wait_no_slave = 1

上面的配置中:

在半同步复制中,有损半同步复制是MySQL5.7版本以前的半同步复制机制,这种半同步复制在Master发生宕机时,Slave会丢失最后一批提交的数据,若这时Slave提升为Master,可能会发生已经提交的事务不见了,发生了回滚的情况,如下图:

有损半同步

有损半同步在事务提交之后,即步骤4后,等待Slave返回ACK,表示至少有Slave接收到了二进制文件,如果这时二进制文件未发送到Slave,Master就宕机,则此时Slave就会丢失Master已经提交的数据。

而MySQL5.7的无损半同步解决了这个问题,其原理如下:

无损半同步

无损半同步复制WAIT ACK发生在事务提交之前,这样即便Slave没有收到二进制日志,Master就宕机了,由于最后一个事务还没有提交,所以本身这个数据对外也不可见,不存在丢失的问题。

所以,对于任何有数据一致性要求的业务,如电商的核心订单业务、银行、保险、证券等与资金密切相关的业务,务必使用无损半同步复制。这样数据才是安全的、有保障的、即使发送宕机,从机也有一份完整的数据。

多源复制

无论是异步复制还是半同步复制,都是一个Master对应N个Slave。其实MySQL也支持N个Master对应1个Slave,这种架构就称为多源复制。架构如下:

多源复制

上图显示了订单库、库存库、供应商库,通过多源复制同步到同一台MySQL实例上,接着就可以通过MySQL 8.0提供的复杂SQL能力,对业务进行深度的数据分析和挖掘。

延迟复制

前面介绍的复制架构,Slave在接收二进制日志后会尽可能快的回放日志,这样是为了避免主从之间发生延迟。而延迟复制却允许Slave延迟回放接收到的二进制日志,为了避免主服务器的误操作,马上同步到了从服务器,导致数据的完全丢失。

可以通过以下命令配置延迟复制:

CHANGE MASTER TO master_delay=3600

这样就人为设置了Slave落后Master服务器1个小时。

延迟复制在数据库的备份架构设计中非常常见,比如可以配置一个延迟一天的延迟备机,这样本是上说,用户可以得到1份24小时前的快照。

那么当线上发送误操作,如DROP TABLE、DROP DATEBASE这样灾难性的命令时,用户有一个24小时前的快照,数据可以快速恢复。

对于金融行业来说,延迟复制是你备份设计中,必须考虑的架构部分。

总结

复制是数据同步的基础,而二进制日志就是复制的基石。

上一篇下一篇

猜你喜欢

热点阅读