业务上线除了表和索引的结构设计之外,你还要做好高可用的设计,因为在真实的生产环境下,如果发生物理硬件故障,没有搭建高可用架构,会导致业务完全不可用。这在海量并发访问的互联网业务中完全无法不敢想象。接下来就来学习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:
- Master服务器会把数据变更产生的二进制日志通过Dump线程发送个Slave服务器
- Slave服务器中的I/O线程负责接收二进制日志,并保存为中继日志
- SQL/Worker线程负责并行执行中继日志,即在Slave服务器上回放Master产生的日志
得益于二进制日志,MySQL的复制相比其他关系型数据库,如Oracle、PostgreDB等,非常灵活,用户可以根据自己的需要购机所需的复制拓扑结构,比如:
复制拓扑结构在上图中,Slave1、Slave2、Slave3 都是 Master 的从服务器,而 Slave11 是 Slave1 的从服务器,Slave1 服务器既是 Master 的从机,又是 Slave11 的主机,所以 Slave1 是个级联的从机。同理,Slave3 也是台级联的从机。
在了解完复制的基本概念后,我们继续看如何配置 MySQL 的复制吧。
MySQL复制配置
搭建 MySQL 复制实现非常简单,基本步骤如下:
-
创建复制所需的账号和权限;
-
从 Master 服务器拷贝一份数据,可以使用逻辑备份工具 mysqldump、mysqlpump,或物理备份工具 Clone Plugin;
-
通过命令 CHANGE MASTER TO 搭建复制关系;
-
通过命令 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
上面的配置中:
-
第 1 行要求数据库启动时安装半同步插件;
-
第 2、3 行表示分别启用半同步 Master 和半同步 Slave 插件;
-
第 4 行表示半同步复制过程中,提交的事务必须至少有一个 Slave 接收到二进制日志。
在半同步复制中,有损半同步复制是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小时前的快照,数据可以快速恢复。
对于金融行业来说,延迟复制是你备份设计中,必须考虑的架构部分。
总结
复制是数据同步的基础,而二进制日志就是复制的基石。
- 二进制日志记录了所有对于MySQL变更的操作
- 可以通过命令SHOW BINLOG EVENTS IN 查看二进制日志的基本信息
- 可以通过工具mysqlbinlog查看二进制日志的详细信息
- 复制搭建很简单,但别忘记配置crash safe相关参数,否则可能导致主从数据不一致
- 异步复制用于非核心场景,不要求数据一致性
- 无损半同步复制用于核心业务场景,保障数据的一致性
- 多源复制可将多个Master数据汇总到一个数据库实例中进行分析
- 延迟复制主要用于误操作防范,金融行业要特别考虑这样的场景
网友评论