关于mysql:深入了解MySQL主从复制的原理

40次阅读

共计 8016 个字符,预计需要花费 21 分钟才能阅读完成。

欢送微信关注「SH 的全栈笔记

0. 主从复制

首先主从复制是什么?简略来说是让一台 MySQL 服务器去复制另一台 MySQL 的数据,使两个服务器的数据保持一致。

这种形式与 Redis 的主从复制的思路没有太大的出入。如果你对 Redis 的主从复制感兴趣能够去看看《Redis 的主从复制》。那既然 Redis 和 MySQL 都采纳了 复制 这种形式,主从复制所带来的意义是什么呢?

通过复制性能,构建一个或者多个从库,能够进步数据库的 高可用性 可扩展性 ,同时实现 负载平衡。当主库产生故障时,能够疾速的切到其某一个从库,并将该从库晋升为主库,因为数据都一样,所以不会影响零碎的运行;当 MySQL 服务器须要扛住更多的读申请时,能够把读申请的流量分流到各个从库下来,写申请则转发给主库,造成读写拆散的架构,来提供更好的读扩大和申请的负载平衡。

读写拆散的架构利用的其实十分宽泛,就比方 MySQL,还有 Redis,以及咱们相熟的 Zookeeper,Zookeeper 的 Follower 收到读申请不会本人解决,而是会将读申请转发给 Leader,感兴趣的能够本人下来理解一下,这里就不偏题了。

1. 复制原理

MySQL 的主从复制反对两种形式:

  • 基于
  • 基于 语句

基于语句的复制在 MySQL3.23 中就曾经有了,而基于语句的形式则在 5.1 中才实现。其本质都是基于主库的 binlog 来实现的,主库记录 binlog,而后从库将 binlog 在本人的服务器上重放,从而保障了主、从的数据一致性。

1.1 binlog

MySQL 中日志分为两个维度,一个是 MySQL 服务器 的,一个是底层 存储引擎 的。而上文提到的 binlog 就是属于 MySQL 服务器的日志,binlog 也叫二进制日志,记录了所有对 MySQL 所做的更改。

基于行、语句的复制形式跟 binlog 的存储形式有关系。binlog 有三种存储格局,别离是 Statement、Row 和 Mixed。

  • Statement 基于语句,只记录对数据做了批改的 SQL 语句,可能无效的缩小 binlog 的数据量,进步读取、基于 binlog 重放的性能
  • Row 只记录被批改的行,所以 Row 记录的 binlog 日志量一般来说会比 Statement 格局要多。基于 Row 的 binlog 日志十分残缺、清晰,记录了所有数据的变动,然而毛病是可能会十分多,例如一条 update 语句,有可能是所有的数据都有批改;再例如 alter table 之类的,批改了某个字段,同样的每条记录都有改变。
  • Mixed Statement 和 Row 的联合,怎么个结合法呢。例如像 update 或者 alter table 之类的语句批改,采纳 Statement 格局。其余的对数据的批改例如 updatedelete采纳 Row 格局进行记录。

为什么会有这么多形式呢?因为 Statement 只会记录 SQL 语句,然而并不能保障所有状况下这些语句在从库上可能正确的被重放进去。因为可能程序不对。

MySQL 什么时候会记录 binlog 呢?是在事务提交的时候,并不是依照语句的执行程序来记录,当记录完 binlog 之后,就会告诉底层的存储引擎提交事务,所以有可能因为语句程序谬误导致语句出错。

1.2 查看 binlog

这里拿 MySQL 5.6 举例子,binlog 默认是处于敞开状态的。咱们能够通过命令show variables like '%log_bin%' 来查看对于 binlog 的配置。

log_bin代表是否开启了 binlog,其默认值为OFF

  • log_bin 代表是否开启了 binlog,其默认值为OFF
  • log_bin_basename binlog 存储文件的残缺名称,会在默认的文件名前面增加上 递增 的序号,就例如mysql-bin.000001
  • log_bin_index binlog 索引文件名称,例如mysql-bin.index
  • sql_log_bin 在 binlog 开启的时候,能够禁用以后 session 的 binlog

你能够在 MySQL 中通过命令 show binary logs 查看所有的 binlog 文件

晓得了有哪些文件之后咱们能够来看看 binlog 文件中的内容,能够在 MySQL 通过 show binlog events 命令来查看。

show binglog events 查看第一个 binlog 文件,咱们也能够通过 in 参数来指定,假如咱们想看的文件名是 mysql-bin.000001,那么能够应用命令show binlog events in 'mysql-bin.000001' 来查看指定的 binlog 文件

接下来咱们来看看咱们在 MySQL 中的操作所对应的 binlog 内容别离是什么。

初始化

咱们下面提到过,binlog 是由一个一个的 event 组成的。从 MySQL 5.0 开始,binlog 的 第一个 event 都为Format_desc,位于图中的Event_type 那一列。能够看到内容为Server ver;5.6.50-log, Binlog ver: 4,阐明以后应用的 MySQL 版本为 5.6.50,Binlog 的版本是 V4。

创立数据库

而后我创立了一个名为 student 的 DB,其 Event_type 是Query,这个 event 的内容为CREATE DATABASE student DEFAULT CHARACTER SET = utf8mb4,一个建库语句。

新建表

而后我创立了一个名为 student 的表,Event_type 也是Query,内容为use student; CREATE TABLE student (id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT),一个建表语句。

插入数据

而后咱们执行 INSERT 语句给该表插入两行数据,再次查看 binlog。

INSERT INTO `student` (`id`, `name`) VALUES (NULL, '张三');
INSERT INTO `student` (`id`, `name`) VALUES (NULL, '李四');

能够看到每次 INSERT 都会开启一个事务,你可能会纳闷,咱们只是简略的执行了 INSERT 语句,没有显示的开启事务。那为什么会有事务产生呢?

这是因为 MySQL 采纳了主动提交(AUTOCOMMIT)的机制,我应用的 InnoDB 存储引擎,是反对事务的,所有的用户流动都产生在事务中。咱们能够通过 show variables like '%AUTOCOMMIT%'; 命令查看,如果后果是 ON 则代表是开启的。

1.3 复制的外围步骤

咱们假如主库曾经开启了 binlog,并失常的记录 binlog。

首先从库启动I/ O 线程,跟主库建设客户端连贯。

主库启动 binlog dump 线程,读取主库上的 binlog event 发送给从库的 I / O 线程,I/ O 线程获取到 binlog event 之后将其写入到本人的 Relay Log 中。

而后从库启动SQL 线程,将 Relay 中的数据进行重放,实现从库的数据更新。

总结来说,主库上只会有一个线程,而从库上则会有两个线程。

1.4 Relay Log

relay log 其实和 binlog 没有太大的区别,在 MySQL 4.0 之前是没有 Relay Log 这部分的,整个过程中只有两个线程。然而这样也带来一个问题,那就是复制的过程须要同步的进行,很容易被影响,而且效率不高。例如主库必须要期待从库读取完了能力发送下一个 binlog 事件。这就有点相似于一个阻塞的信道和非阻塞的信道。

阻塞信道 就跟你在柜台一样,你要递归柜员一个货色,然而你和柜员之间没有能够放货色的中央,你就只能始终把文件拿着,直到柜员接手;而 非阻塞信道 就像你们之间有个中央能够放文件,你就间接放上去就好了,不必等柜员接手。

引入了 Relay Log 之后,让本来同步的获取事件、重放事件解耦了,两个步骤能够异步的进行,Relay Log 充当了缓冲区的作用。Relay Log 有一个 relay-log.info 的文件,用于记录以后复制的进度,下一个事件从什么 Pos 开始写入,该文件由 SQL 线程负责更新。

1.5 Relay Log 外围参数

接下来让咱们理解一下 Relay Log 的外围参数。

  • max_relay_log_size 中继日志的最大 size,默认值 0,如果为 0 就会取默认的 size 1G,否则就为设置的值
  • relay_log 定义 relay 的名称,默认为主机名 +relay-bin,例如像hostname-relay-bin
  • relay_log_basename 中继日志的全门路,即门路 + 文件名,例如/path/to/hostname-relay-bin,最大长度为 256
  • relay_log_index 定义中继日志的索引文件的全门路,同样其最大的长度为 256. 其默认值为 hostname + relay-bin.index,例如/path/to/hostname-relay-bin.index
  • relay_log_info_file 定义 relay-log.info 文件的名称
  • relay_log_info_repository 寄存 relay log 重放的数据的形式,能够设置为 FILETABLE。FILE 代表将中继日志重放的数据记录在 relay-info.log 中,TABLE 则将其寄存在 slave_relay_log_info 这张表里。
  • relay_log_purge 是否主动清空不须要的中继日志,默认值为ON
  • relay_log_recovery 当从库宕机后,如果 relay log 损坏了导致局部的中继日志没有进行同步,则主动放弃所有未进行重放的中继日志,并从主库从新获取,默认值为OFF
  • relay_log_space_limit 设置中继日志的最大值,避免写满磁盘。然而不倡议设置这个值,倡议还是给中继日志须要的空间,0就是不限度,0也是默认值
  • sync_relay_log 用于管制中继日志写入磁盘的变量,假如值为 n,那么在中继日志每承受 n 次 binlog 事件之后就会调用 fdatasync()函数将中继日志强制的刷入磁盘;相同,如果值为 0,则写入 OS 的缓冲区内,由 OS 调度决定何时将中继日志刷入磁盘,这样一来如果在没有刷入之前报错了,那么中继日志就会失落。默认值是10000,也就是每向中继日志中写入 1w 次 binlog 事件就将中继日志强制的刷入磁盘。
  • sync_relay_log_info 该参数的影响跟参数 relay_log_info_repository 有肯定关系,同时也跟是否应用反对事务的存储引擎有关系。该值默认也是10000.

    • sync_relay_log_info 为 0 时

      • relay_log_info_repository为 FILE,MySQL 不会调用 fdatasync(),而是将刷入磁盘的调度交给 OS;
      • relay_log_info_repository为 TABLE,如果应用了反对事务的存储引擎,则每次事务的时候该表都会被更新;如果没有应用事务引擎,则永远不会被更新
    • sync_relay_log_info 大于 0 时

      • relay_log_info_repository为 FILE,假如设置的值为 N,那么 每 N 次事务 都会都会调用 fdatasync()强制将 relay-log.info 刷入磁盘
      • relay_log_info_repository为 TABLE,如果应用了反对事务的引擎,则该表 每次事务 完结都会被更新;如果没有应用事务引擎则会在 写入 N 个 binlog 事件 的时候更新该表。

2. 复制模型

平时的开发中,其实很少说一上来就间接搞主从架构的。费时间、费钱还引入了额定的复杂度,最初发现投入了这么多一个单 MySQL 服务器就齐全能 handle。

这就跟一个产品的架构迭代是一样的,刚刚起步的时候一个单体利用足够了。当你的业务扩大,申请收缩,单体无奈抗住压力了,就会思考开始部署多实例,开始采纳微服务架构去做横向扩大、负载平衡。

2.1 一主多从

当然你也能够把它当成 一主一从

这是最简略的模型,特地适宜大量写、大量读的状况。读申请被分到了各个从库上,无效的帮主库扩散了压力,可能晋升 读并发 。当然,你也能够只是把从库当成一个灾备库,除了 主从复制 之外,没有其余任何的申请和数据传输。

甚至你能够把其中一个备库作为你的预发环境的数据库,当然,这说到底还是间接动了生产环境的数据库,是一种过于现实的用处,因为这还波及到生产环境数据库的数据敏感性。不是所有人都可能接触到的,须要有欠缺的权限机制。

值得注意的是,如果有 n 个从库,那么主库上就会有 n 个 binlog dump 线程。如果这个 n 比拟大的话在复制的时候可能会造成主库的性能抖动。所以在从库较多的状况下能够采纳级联复制。

2.2 级联复制

级联复制用大白话说就是 套娃

原本从库 B、C、D、E、F、G 都是复制的主库 A,然而当初因为 A 的压力比拟大,就不这么干了,调整成了如下的模式。

  • B、C 复制 A
  • D、E 复制 B
  • F、G 复制 C

这就叫级联复制,开启疯狂套娃模式。你甚至会感觉这种套娃很眼生,在 Redis 主从复制中也能够采纳级联模式,slave 去复制另一个 slave。

级联复制的益处在于很大水平上加重了主库的压力,主库只须要关怀与其有间接复制关系的从库,剩下的复制则交给从库即可。相同,因为是这种层层嵌套的关系,如果在较下层呈现了谬误,会影响到挂在该服务器下的所有子库,这些谬误的影响成果被放大了。

2.3 主主复制

顾名思义,就是两个主库互相复制,客户端能够对任意一台主库进行写操作。任何一台主库服务器上的数据产生了变动都会同步到另一台服务器下来。有点相似于 Eureka Server 的双节点模式,两个注册核心互相注册。这样一来,任何一台挂了都不会对系统产生影响。

而且主主复制能够突破数据库性能瓶颈,一个很酷的性能——横向扩大。为什么说很酷呢,如果 DB 能做到横向扩大,那很多被数据库并发所限度的瓶颈都能够被冲破,然而 …

然而主主复制其实并不牢靠,两边的数据抵触的可能性很大。例如复制进行了,零碎依然在向两个主库中写入数据,也就是说一部分数据在 A,另一部分的数据在 B,然而没有互相复制,且数据也不同步了。要修复这部分数据的难度就会变得相当大。

所以我认为双主的更多的意义在于 HA,而不是负载平衡。

2.4 主、被动的主主复制

同样还是双主的构造,然而区别在于其中一台是 只读 的被动服务器,客户端不会向该库进行写操作。

其用处在哪里呢?例如咱们要在不中断服务的前提下对 MySQL 进行保护、优化,举个例子——批改表构造 。假如咱们有两个数据库,主库 A 和被动主库 B,留神此处的被动主库是 只读 的,咱们先进行 A 对 B 的复制,也就是停掉 A 上的 SQL 线程。

这样一来,咱们之后在 B 上执行的十分耗时、可能须要锁表的操作就不会立刻同步到 A 上来。因为此时 A 正在对外提供服务,所以不能使其收到影响,然而因为采纳的是异步的复制模式,所以 Relay Log 还是持续由 I / O 线程写入,只是不去进行重放。

而后咱们在 B 上执行此次的保护操作,留神,此时 A 下面产生的更新还是会失常的同步到 B 来。执行完后 替换读写的角色。也就是让 A 变成只读的被动主库,而 B 变为被动主库对外提供服务。

而后从新开启 SQL 线程,A 开始去对之前 Relay Log 中积攒的 event 进行重放。尽管 A 此时可能会阻塞住,然而 A 曾经没有对外提供服务了,所以没有问题。

主、被动下的主主模式的益处大家也就分明了,能够在不进行服务的状况上来做数据库的构造更新,其次能够在主库产生故障的状况下,疾速的切换,保障数据库的 HA。

3. 复制形式

上文咱们不止一次的提到了 复制是异步的,接下来咱们来理解一下 MySQL 的主从复制都有哪些形式。

3.1 异步复制

首先就是异步,这也是 MySQL 默认的形式。在异步复制下,主库不会被动的向从库发送音讯,而是期待从库的 I / O 线程建设连贯,而后主库创立 binlog dump 线程,把 binlog event 发送给 I / O 线程,流程如下图。

主库在执行完本人的事务、记录完 binlog 之后就会间接返回,不会与客户端确认任何后果。而后后续由 binlog dump 线程异步的读取 binlog,而后发送给从库。解决申请 主从复制 是两个齐全异步化的过程。

3.2 同步复制

同步模式则是,主库执行一个事务,那么主库必须期待所有的从库全副执行完事务返回 commit 之后能力给客户端返回胜利,

值得注意的是,主库会间接提交事务,而不是期待所有从库返回之后再提交。MySQL 只是提早了对客户端的返回,并没有延后事务的提交。

同步模式用脚趾头想晓得性能会大打折扣,它把客户端的申请和主从复制耦合在了一起,如果有某个从库复制线程执行的慢,那么对客户端的响应也会慢很多。

3.3 半同步复制

半同步绝对于同步的区别在于,同步须要期待所有的从库 commit,而半同步只须要一个从库 commit 就能够返回了。如果超过默认的工夫依然没有从库 commit,就会切换为异步模式再提交。客户端也不会始终去期待了。

因为即便前面主库宕机了,也能至多保障有一个从库节点是能够用的,此外还缩小了同步时的等待时间。

4. 复制中的数据一致性

咱们在 1.3 中探讨了复制的外围步骤,看似很简略的一个流程,主库的 binlog dump 去读取 binlog,而后从库的 I / O 线程去读取、写入 Relay Log,进而从库的 SQL 线程再读取 Relay Log 进行重放。

那如果 I / O 线程复制到一半本人忽然挂掉了呢?又或者复制到一半主库宕机了呢?如果和保证数据一致性的呢?

咱们下面提到过,有一个 relay-log.info 的文件,用于记录以后从库正在复制的 binlog 和写入的 Relay Log 的 Pos,只有这个文件还在,那么当从库意外重启之后,就会从新读取文件,从上次复制的中央开始持续复制。这就跟 Redis 中的主从复制相似,单方要保护一个 offset,通过比照 offset,来进行 psync 增量数据同步。

然而在 MySQL 5.5 以及之前,都只能将复制的进度记录在 relog-log.info 文件中。换句话说,参数 relay_log_info_repository 只反对 FILE,能够再回到下面的1.5 Relay Log 外围参数 看一下。所以只有在 sync_relay_log_info 次事务之后才会把 relay-log.info 文件刷入磁盘。

如果在刷入磁盘之前从库挂了,那么重启之后就会发现 SQL 线程理论执行到地位和数据库记录的不统一,数据一致性的问题就这么产生了。

所以在 MySQL 5.6 时,参数 relay_log_info_repository 反对了 TABLE,这样一来咱们就能够将复制的进度放在零碎的mysql.slave_relay_log_info 表里去,并且把更新进度、SQL 线程执行用户事务绑定成一个事务执行。即便 slave 宕机了,咱们也能够通过 MySQL 内建的解体复原机制来使理论执行的地位和数据库保留的进度复原到统一。

其次还有下面提到的半同步复制,主库会先提交事务,而后期待从库的返回,再将后果返回给客户端,然而如果在主库期待的时候,从库挂了呢?

此时主库上因为事务曾经提交了,然而从库上却没有这个数据。所以在 MySQL 5.7 时引入了 无损半同步复制 ,减少了参数rpl_semi_sync_master_wait_point 的值,在 MySQL 5.7 中值默认为after_sync,在 MySQL 5.6 中默认值为after_commit

  • after_sync 主库先不提交事务,期待某一个从库返回了后果之后,再提交事务。这样一来,如果从库在没有任何返回的状况下宕机了,master 这边也无奈提交事务。主从依然是统一的
  • after_commit 与之前探讨的一样,主库先提交事务,期待从库返回后果再告诉客户端

好了以上就是本篇博客的全部内容了,如果你感觉这篇文章对你有帮忙,还麻烦 点个赞 关个注 分个享 留个言

欢送微信搜寻关注【SH 的全栈笔记】,查看更多相干文章

正文完
 0