美文网首页
MySQL实战宝典 高可用架构篇 15 MySQL 复制:最简单

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

作者: 逢春枯木 | 来源:发表于2021-06-21 23:49 被阅读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:

    • 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 复制实现非常简单,基本步骤如下:

    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
    

    上面的配置中:

    • 第 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数据汇总到一个数据库实例中进行分析
    • 延迟复制主要用于误操作防范,金融行业要特别考虑这样的场景

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/phtpyltx.html