Full-Stack-Notes/notes/MySQL_复制.md
2019-08-19 08:00:25 +08:00

296 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# MySQL 复制
## 一、日志格式
### 1.1 二进制日志格式
MySQL 二进制日志是进行主从复制的基础,它记录了所有对 MySQL 数据库的修改事件,包括增删改查和表结构修改。当前 MySQL 一共支持三种二进制日志格式,可以通过 binlog-format 参数来进行控制,其可选值如下:
+ **STATEMENT**:段格式。是 MySQL 最早支持的二进制日志格式。其记录的是实际执行修改的 SQL 语句,因此在进行批量修改时其所需要记录的数据量比较小,但对于 UUID() 或者其他依赖上下文的执行语句,可能会在主备上产生不一样的结果。
+ **ROW**:行格式,是 MySQL 5.7 版本之后默认的二进制日志格式。其记录的是修改前后的数据。因此在批量修改时其需要记录的数据量比较大,因为需要记录每一行语句修改后的结果。但其安全性比较高,不会导致主备出现不一致的情况。同时因为 ROW 格式是在从库上直接应用更改后的数据,其还能减少锁的使用。
+ **MIXED**:是以上两种日志的混合方式,默认采用段格式进行记录,当段格式不适用时 (如 UUID() ),则默认采用 ROW 格式。
通常在主备之间网络情况良好的时,可以优先考虑使用 ROW 格式,此时数据一致性最高,其次是 MIXED 格式。在设置 ROW 格式时,还有一个非常重要的参数 binlog_row_image 。
### 1.2 binlog_row_image
binlog_row_image 有以下三个可选值:
+ **full**:默认值,记录行在修改前后所有列的值。
+ **minimal**:只记录修改涉及列的值。
+ **noblob**:与 full 类似,但如果 BLOB 或 TEXT 列没有修改,则不对其进行记录。
binlog-format 和 binlog_row_image 的默认值可能在不同版本存在差异,可以使用以下命令进行查看。通常情况下,为了减少在主备复制中需要传输的数据量,可以将 binlog_row_image 的值设置为 minimal 或 noblob。
```sql
show variables like 'binlog_format';
show variables like 'binlog_row_image';
```
## 二、基于二进制日志的复制
### 2.1 复制原理
MySQL 的复制原理如下图所示:
+ 主库首先将变更写入到自己的二进制日志中;
+ 备库会启动一个 IO 线程,然后主动去主库节点上获取变更日志,并写入到自己的中继日志中。
+ 之后从中继日志中读取变更事件,在从库上执行变更。
+ 当备库与主库数据状态一致,备库的 IO 线程就会进入睡眠。当主库再次发生变更时,其会向备库发出信号,唤醒 IO 线程并再次进行工作。
如果没有进行任何配置,主库在将变更写入到二进制日志后,就会返回对客户端的响应,因此默认情况下的复制是完全异步进行的,主备之间可能会短暂存在数据不一致的情况。
![mysql-replication](D:\Full-Stack-Notes\pictures\mysql-replication.jpg)
### 2.2 配置步骤
首先主节点需要开启二进制日志,并且在同一个复制环境下,主备节点的 server-id 需要不一样:
```properties
[mysqld]
server-id = 226
# 开启二进制日志
log-bin=mysql-bin
```
在备份节点配置中继日志:
```properties
[mysqld]
server-id = 227
# 配置中继日志
relay_log = mysql-relay-bin
# 为了保证数据的一致性,从节点应该设置为只读
read_only = 1
# 以下两个配置代表是否开启二进制日志,如果该从节点还作为其他备库的主节点,则开启,否则不用配置
log-bin = mysql-bin
# 是否将中继节点收到的复制事件写到自己的二进制日志中
log_slave_updates = 1
```
创建用于进行复制账号,并为其授予权限:
```shell
CREATE USER 'repl'@'192.168.0.%' IDENTIFIED WITH mysql_native_password BY '123456';
GRANT REPLICATION SLAVE on *.* TO 'repl'@'192.168.0.%' ;
```
查看主节点二进制日志的状态:
```shell
mysql> SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 887 | | | |
+------------------+----------+--------------+------------------+-------------------+
```
基于日志和偏移量,建立复制链路:
```shell
CHANGE MASTER TO MASTER_HOST='192.168.0.226',\
MASTER_USER='repl', \
MASTER_PASSWORD='123456',\
MASTER_LOG_FILE='mysql-bin.000001',\
MASTER_LOG_POS=887;
```
开始复制:
```shell
START SLAVE;
```
查看从节点复制状态,主要参数有 Slave_IO_Running 和 Slave_SQL_Running其状态都为 Yes 表示用于进行复制的 IO 进程已经开启。Seconds_Behind_Master 参数表示从节点复制的延迟量。此时可以在主库上进行任意更改,并在备库上查看情况。
```shell
mysql> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.0.226
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000001
Read_Master_Log_Pos: 887
Relay_Log_File: mysql-relay-bin.000002
Relay_Log_Pos: 322
Relay_Master_Log_File: mysql-bin.000001
# Slave_IO_Running: Yes
# Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 887
Relay_Log_Space: 530
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
# Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 226
# Master_UUID: e1148574-bdd0-11e9-8873-0800273acbfd
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
Master_public_key_path:
Get_master_public_key: 0
Network_Namespace:
1 row in set (0.00 sec)
```
### 2.3 优缺点
基于二进制日志的复制是 MySQL 最早使用的复制技术,因此 MySQL 对其的支持比较完善,对执行修改的 SQL 语句几乎没有任何限制。其主要的缺点是在一主多从的高可用复制架构中,如果主库发生宕机,此时想要自动通过从库的日志和偏移量来确定新的主库比较困难。
## 三、基于 GTID 的复制
### 2.1 GTID
MySQL 5.6 版本之后提供了一个新的复制模式:基于 GTID 的复制。GTID 全称为 Global Transaction ID即全局事务 ID。它由每个服务节点的唯一标识和其上的事务 ID 共同组成,格式为: *server_uuid : transaction_id* 。通过 GTID ,可以保证在主库上的每一个事务都能在备库上得到执行,不会存在任何疏漏。
### 2.2 配置步骤
主从服务器均增加以下 GTID 的配置:
```properties
gtid-mode = ON
# 防止执行不受支持的语句,下文会有说明
enforce-gtid-consistency = ON
```
如果配置过上面的基于二进制日志的复制,还需要在从服务器上执行以下命令,关闭原有复制链路:
```shell
STOP SLAVE IO_THREAD FOR CHANNEL '';
```
建立新的基于 GTID 复制链路,指定 MASTER_AUTO_POSITION = 1 表示由程序来自动确认开始同步的 GTID 的位置:
```shell
CHANGE MASTER TO MASTER_HOST='192.168.0.226',\
MASTER_USER='repl',
MASTER_PASSWORD='123456',
MASTER_AUTO_POSITION=1;
```
开始复制:
```shell
START SLAVE;
```
在主节点上执行任意修改操作并查看从节点状态关键的输出如下Retrieved_Gtid_Set 代表从主节点上接收到的两个事务Executed_Gtid_Set 表示这两个事务已经在从库上得到执行。
```sql
mysql> SHOW SLAVE STATUS\G
....
Master_UUID : e1148574-bdd0-11e9-8873-0800273acbfd
Retrieved_Gtid_Set : e1148574-bdd0-11e9-8873-0800273acbfd:1-2
Executed_Gtid_Set : e1148574-bdd0-11e9-8873-0800273acbfd:1-2
.....
```
### 2.3 优缺点
GTID 复制的优点在于程序可以自动确认开始复制的 GTID 点。但其仍然存在以下限制:
+ 不支持 CREATE TABLE ... SELECT 语句。 因为在 ROW 格式下,该语句将会被记录为具有不同 GTID 的两个事务,此时从服务器将无法正确处理。
+ 事务,过程,函数和触发器内部的 CREATE TEMPORARY TABLE 和 DROP TEMPORARY TABLE 语句均不受支持。
为防止执行不受支持的语句,建议配置和上文配置一样,开启 enforce-gtid-consistency 属性, 开启后在主库上执行以上不受支持的语句都将抛出异常并提示。
## 四、半同步复制
## 五、高可用架构
不论是主主复制结构,还是一主多从复制结构,单存依靠复制只能解决数据可靠性的问题,并不能解决系统高可用的问题,想要保证高可用,系统必须能够自动进行故障转移,即在主库宕机时,主动将其它备库升级为主库。常用的有以下两种解决方案:
### 4.1 MMM
1. Monitor 检测到 Master1 连接失败;
2. Monitor 发送 set_offline 指令到 Master1 的 Agent
3. Master1 Agent 如果存活,下线写 VIP尝试把 Master1 设置为 read_only=1
4. Moniotr 发送 set_online 指令到 Master2
5. Master2 Agent 接收到指令,执行 `select master_pos_wait()` 等待同步完毕;
6. Master2 Agent 上线写 VIP把 Master2 节点设为 read_only=0
7. Monitor 发送更改同步对象的指令到各个 Slave 节点的 Agent
8. 各个 Slave 节点向新 Master 同步数据;
简单来说MMM 就是暴力的将 Master1 切换到 Master2 ,它本生不会尝试去补齐丢失的数据。
### 4.2 MHA
1. 尝试从宕机 Master 中保存二进制日志;
2. 找到含有最新中继日志的 Slave
3. 把最新中继日志应用到其他实例,实现各实例数据一致;
4. 应用从 Master 保存的二进制日志事件;
5. 提升一个 Slave 为 Master
6. 其他 Slave 向该新 Master 同步。
当 Master 宕机后, MHA 会尝试保存宕机 Master 的二进制日志,然后自动判断 MySQL 集群中哪个实例的中继日志是最新的,并将有最新日志的实例的差异日志传到其他实例补齐,从而实现所有实例数据一致。然后把宕机 Master 的二进制日志应用到选定节点,并提升为 Master 。
从切换流程流程可以看到,如果宕机 Master 主机无法 SSH 登录,那么第一步就没办法实现,对于 MySQL5.5 以前的版本,数据还是有丢失的风险。对于 5.5 后的版本,开启**半同步复制**后,真正有助于避免数据丢失,半同步复制保证至少一个 不是所有slave 在 master 提交时接收到二进制日志事件。因此,对于可以处理一致性问题的 MHA 可以实现"几乎没有数据丢失"和"从属一致性"。
### 优点
- 开源,用 Perl 编写
- 方案成熟故障切换时MHA 会做日志补齐操作,尽可能减少数据丢失,保证数据一
- 部署不需要改变现有架构
### 限制
- 各个节点要打通 SSH 信任,有一定的安全隐患
- 没有 Slave 的高可用
- 自带的脚本不足例如虚IP配置需要自己写命令或者依赖其他软件
- 需要手动清理中继日志
## 参考资料
+ [MHA高可用架构](https://segmentfault.com/a/1190000017486693)
+ [MMM高可用架构](https://segmentfault.com/a/1190000017286307)
+