MySQL学习之主从复制原理_binlog

在前面的学习中,经常会提到 binlog 。知道binlog可以用来做归档和主从同步。他里面到底存了什么?怎么实现同步的呢?

binlog 的格式及对比

binlog的格式

binlog 的存储格式有三种,分别是Statement,Row,Mixed

  • Statement:基于SQL语句级别的,每条操纵的SQL语句会保存在 binlog 中。
  • Row:基于行级别的,每一行数据发生变化都会记录到 binlog 中,但是不会记录原始语句。因此它会记录的非常详细,日志量也比statement格式记录的多得多
  • Mixed:是前两种格式的混合。

记录binlog,首先确保 binlog 是开启状态,检查是否开启的命令:

  show variables like 'log_bin';
  
  +---------------+-------+
  | Variable_name | Value |
  +---------------+-------+
  | log_bin       | ON    |
  +---------------+-------+

注: 如果 binlog 是关闭状态(OFF), 剩下的操作都是看不到结果的。

查看binlog文件有以下几种方式进行查看。

  show binlog events; # 只查看第一个binlog文件内容
  show binlog events in 'mysql-bin.000001'; # 查看指定文件
  show binary log; # 查看binlog文件列表
  show master status; # 查看当前正在写入的binlog文件

格式对比

MySQL学习之主从复制原理_MySQL_02Statement 和 Row 格式

可以看到 Statement 格式记录的是完整的语句原文,而 Row 格式记录的是操作什么表和什么行为。

Row格式看到 Event_type 中记录了,一个是 Table_map event 操作表 ,一个是 Delete_rows event 行为。

Row 格式不能直接进行查看详细信息的,需要借助mysqlbinlog工具进行解析和查看内容。语法是:

  # -vv               显示的SQL语句增加了注释
  # -start_position   是开始位置
  # 还有很多参数, 请自行查看文档
  mysqlbinlog -vv master.000001 --start_position=4 

MySQL学习之主从复制原理_朱从复制_03

使用mysqlbinlog解析的update操作

MySQL学习之主从复制原理_朱从复制_04

使用mysqlbinlog解析的delete操作

可以看到 Row 格式占用空间很大,这要是更新或者删除几十万行数据,用 Statement 一条语句解决战斗。而使用 Row 不仅占用磁盘空间,并且消耗 IO 资源,影响执行性能。

看到这里,是不是觉得 Statement 就完美了呢?其实不然,Statement 在记录时,可能会造成主从不一致的情况。

为什么会出现这个情况呢?因为他不够精确,在主库和从库上执行一条语句,可能走得索引不一样,操作的id就可能会出现错误,就造成了数据安全风险。

此时也就是 Mixed 诞生的作用,利用 Statement 的优点,MySQL在执行语句时,会进行判断该语句是否有风险。如果有风险,则使用 Row 格式,否则就用 Statement 格式。

主从复制原理

了解了binlog的一些格式,就可以看下主从切换流程。

MySQL学习之主从复制原理_朱从复制_05主从流程图

MySQL 主从复制中主要有三个线程,master(binlog dump thread)、slave(io thread, sql thread)

主库会生成一个 log dump 线程,用来给从库 I/O 线程传 Binlog 数据。

从库的 I/O 线程会去请求主库的 Binlog,并将得到的 Binlog 写到本地的 relay log (中继日志)文件中。

SQL 线程,会读取 relay log 文件中的日志,并解析成 SQL 语句逐一执行。

  • 主库中 log dump 线程

从库进行连接的时候,主库会为会为从库生成一个长链接,就是 log dump 线程。该线程主要工作就是通知从库有数据操作,并将相应的 binlog 发送给从库(Push模式)。

一主多从,就会产生多个 log dump 线程。在读取 binlog 中的操作时,log dump 线程会对主库上的 Binlog 加锁;当读取完成发送给从库之前,锁会被释放。

  • 从库中 I/O 线程

当从库上执行start slave命令之后, 从库会创建 I/O 线程, 用来连接主库,接收主库传递过来更新的 binlog ,相继写入到本地文件 中继日志(relay log)中。

  • 从库中 relay log 日志

从库 I/O 线程将主库的 Binlog 日志读取过来,解析各类 Events(event_type) 之后记录到从库本地文件,这个文件就被称为 relay log。

SQL 线程会读取 relay log 日志的内容并应用到从库,从而使从库和主库的数据保持一致。中继日志充当缓冲区,这样 master 就不必等待 slave 执行完成才发送下一个事件。

  • 从库中 SQL 线程

SQL 线程负责读取 relay log 中的内容,解析成具体的操作并执行,最终保证主从数据的一致性。

对于每一个主从连接,都需要这三个进程来完成。当主节点有多个从节点时,主节点会为每一个当前连接的从节点建一个 log dump 进程,而每个从节点都有自己的 I/O 进程,SQL 进程。

  • 事务日志的同步过程

1、在从库上是用 change master 命令,设置主库的IP,端口,用户名,密码,文件名和日志偏移量(pos)

2、在从库上执行 start slave 命令,这是从库会启动两个线程,一个是 IO 线程和 SQL 线程。其中 IO 线程是负责和主库进行连接的。

3、主库校验用户名,密码后,开始按照备库传递过来的文件和位置,进行读取binlog,发给从库。

4、从库拿到 binlog 后,写到本地文件,也就是 relay log(中继日志)。

5、SQL 线程开始读取中继日志,解析日历中的命令,执行SQL。

在官方的 5.6 版本之前,MySQL 只支持单线程复制,由此在主库并发高、TPS 高时就会出现严重的主备延迟问题。之后, SQL Theard 支持多线程复制了。

MySQL 主从复制模式

有没有想过一个问题,如果此时,从库在进行接收数据时,主库突然掉电了,或者主库服务挂掉了。有些 binlog 还没有发送给从库,此时数据会不会不一致甚至丢失?

想知道这个问题的原因,就要看下MySQL的复制模式。复制模式是两种:一种是基于 binlog 位点进行复制,一种是基于 GTID 进行复制。

基于 binlog 的复制模式

  • 异步模式(async-mode)

MySQL学习之主从复制原理_binlog_06异步模式图 -- 图片来自网络

该模式MySQL主从复制默认的。主节点不会主动推送数据到从节点,主库在执行完客户端提交的事务后会立即将结果返回给客户端,并不关心从库是否执行成功并处理。

这种模式对客户端响应最快,但是一旦主服务器宕机,此时主节点上已经提交的事务可能没有传到从节点上。强行切换从节点的话,数据可能会因为没有及时的发送给从库导致从服务器丢失。

  • 半同步模式(semi-sync)

MySQL学习之主从复制原理_朱从复制_07半同步模式 -- 图片来自网络

介于全同步复制和异步同步之间,主库在执行完客户端提交的事务后不会立刻返回给客户端,而是等待至少一个从库接收到并写到 relay log 日志中才返回给客户端。

若 master 提交事务受到阻塞,出现等待超时,在一定时间内 master 没被告知已收到,此时 master 自动转换为异步复制机制。

相对于异步复制,半同步复制提高了数据的安全性,同时它也造成了一定程度的延迟,这个延迟最少是一个TCP/IP往返的时间。所以,半同步复制最好在低延时的网络中使用

注: 只能保证传输到一个从节点上,master 和 slave 上必须都开启半同步模式,只开启一端的话,还是异步模式

  • 全同步模式

指当主库执行完一个事务,然后所有的从库都复制了该事务并成功执行完才返回成功信息给客户端。

因为需要等待所有从库执行完该事务才能返回成功信息,所以全同步复制的性能必然会受到严重的影响。

基于 GTID 的复制模式

在传统的复制里,当发生故障时,需要主从切换,需要找到 binlog 和 pos位点信息,然后将其设定为新的主节点开启复制,相对比较麻烦。

在 MySQL 5.6里面,MySQL 会通过内部机制自动匹配 GTID 断点,不再寻找 binlog 和 pos 位点。我们只需要知道 ip, 端口, 用户名, 密码就可以自动复制。

GTID 全称 是 Global Transaction IDentifier 全局事务标识。它具有全局唯一性,一个事务对应一个GTID。

唯一性不仅仅限于主服务器,GTID 在从服务器上也是唯一的。一个 GTID 事务只执行一次,从而避免重复执行导致数据混乱或主从不一致。

  • GTID 原理

基于 GTID 的复制中,从库会告诉主库已经执行的事务 GTID 值,然后主库会将所有未执行的事务 GTID 的列表发给从库。

并且保证同一个事务只在指定的从库执行一次,通过全局的事务 ID 确定从库要执行的事务这种方式替代了以前需要使用 binlog 和 pos 位点确定从库要执行的事务

  • GTID 复制过程

1、主库更新数据时,会在事务前产生 GTID,一同记录到 binlog 中。

2、从库的 I/O 线程 接收到 主库更新的 binlog,写入到本地的中继日志 relay log 中。

从库读取 GTID 是根据 gtid_next 变量,告诉 slave 下一个执行哪个GTID。

3、SQL 线程从中继日志获取 GTID ,然后对比 slave 端的 binlog 是否存在该记录。

4、如果有该记录,说明该 GTID 已经执行过了,则直接忽略,跳过该记录。

5、如果没有该记录,salve 会从中继日志中执行该 GTID 的事务,并记录到 binlog。

  • GTID 组成

GTID 是由两部分组成的,格式是:

  GTID = source_id:transaction_id

source_id: 是产生 GTID 的服务器,即是 server_uuid ,是一个全局唯一的值。

在第一次启动时生成(sql/mysqld.cc: generate_server_uuid()),并保存到DATADIR/auto.cnf文件里

transaction_id: 是顺序化的序列号(sequence number),在每天MySQL服务器上都是从1开始自增的序列,事物的唯一标识。

  • GTID 生成

在 GTID 模式下,每个事务都会跟一个 GTID 一一对应。这个 GTID 有两种生成方式,使用哪种方式生成取决于 gtid_next 的值.

在 Master 上,gtid_next 是默认的 AUTOMATIC,即 GTID 在每次事务提交时自动生成。

它从当前已执行的 GTID 集合(即 gtid_executed)中,找一个大于 0 的未使用的最小值作为下个事务 GTID。

在实际的更新事务记录之前将 GTID 写入到 Binlog。

在 Slave 上,从 Binlog 先读取到主库的 GTID(即 set gtid_next 记录),而后执行的事务采用该 GTID。

主从复制可能出现的问题

  • Salve 同步延迟

因为 Slave 端是通过 I/O thread 单线程来实现数据解析入库;而 Master 端写 Binlog 由于是顺序写效率很高。

当主库的 TPS 很高的时候,必然 Master 端的写效率要高过 Slave 端的读效率,这时候就有同步延迟的问题。

这时可以通过 MySQL 命令:

  show slave status;

查看 Seconds_Behind_Master,可以看到主从复制中间延迟了多久。这个值越大,说明延迟越严重。

基于 binlog 复制方式肯定有这种问题,MySQL 官方在 5.7 版本引入了基于组提交的并行复制方案。

栗子:

比如现在有一个场景,就是我先插入一条数据,然后再把它查出来,然后更新这条数据。

在生产环境高峰期,写并发达到2000/s,这时主从复制可能延迟在大概小几十毫秒。

线上会发现,每天总有那么一些数据,我们期望更新一些重要的状态,但在高峰期还没有更新。

请问怎么解决该问题? 欢迎讨论....

关于主从复制导致的问题,这里在后续学习会做个整体的笔记记录,欢迎各位大佬评论留言讨论。

MySQL学习之主从复制原理_binlog_08