共计 6085 个字符,预计需要花费 16 分钟才能阅读完成。
mysql 复制流程
- 在备库 B 上通过 change master 命令,设置主库 A 的 IP、端口、用户名、明码,以及要从哪个地位开始申请 binlog,这个地位蕴含文件名和日志偏移量
- 在备库 B 上执行 start slave 命令,这时候备库会启动两个线程,就是图中的 io_thread 和 sql_thread。其中 io_thread 负责与主库建设连贯
- 主库 A 校验完用户名、明码后,开始依照备库 B 传过来的地位,从本地读取 binlog,发给 B
- 备库 B 拿到 binlog 后,写到本地文件,称为直达日志(relay log)
- sql_thread 读取直达日志,解析出日志里的命令,并执行
binlog 的三种格局
statement
statement 格局下,记录到 binlog 里的是语句原文
binlog 设置的是 statement 格局,并且语句中有 limit,所以这个命令可能是 unsafe 的,可能会呈现这样一种状况:在主库执行这条 SQL 语句的时候,用的是索引 a;而在备库执行这条 SQL 语句的时候,却应用了索引 t_modified。因而,MySQL 认为这样写是有危险的
在 statement 模式下,因为他是记录的执行语句,所以,为了让这些语句在 slave 端也能正确执行,那么他还必须记录每条语句在执行的时候的一些相干信息,也就是上下文信息,以保障所有语句在 slave 端被执行的时候可能失去和在 master 端执行时候雷同的后果。这须要应用 mysqlbinlog 工具解析进去,而后把解析后果整个发给 MySQL 执行,而不是应该只应用从 statement 解析出的语句来执行,这样会短少上下文
row
当 binlog_format 应用 row 格局的时候,binlog 外面记录了实在影响的行的主键 id
当 binlog_format 应用 row 格局的时候,binlog 外面记录了实在删除行的主键 id,这样 binlog 传到备库去的时候,就必定会删除 id=4 的行,不会有主备删除不同行的问题
row 格局的毛病是,很占空间。比方你用一个 delete 语句删掉 10 万行数据,用 statement 的话就是一个 SQL 语句被记录到 binlog 中,占用几十个字节的空间。但如果用 row 格局的 binlog,就要把这 10 万条记录都写到 binlog 中。这样做,不仅会占用更大的空间,同时写 binlog 也要消耗 IO 资源,影响执行速度
然而当初越来越多的场景要求把 MySQL 的 binlog 格局设置成 row。因为能够进行复原数据
在 row 模式下,binlog 中能够不记录执行的 sql 语句的上下文相干的信息,仅仅只须要记录那一条记录被批改了,批改成什么样了,所以 row 的日志内容会十分分明的记录下每一行数据批改的细节,非常容易了解
mixed
mixed 格局的意思是,MySQL 本人会判断这条 SQL 语句是否可能引起主备不统一,如果有可能,就用 row 格局,否则就用 statement 格局
mixed 格局能够利用 statment 格局的长处,同时又防止了数据不统一的危险
如果你的线上 MySQL 设置的 binlog 格局是 statement 的话,那基本上就能够认为这是一个不合理的设置。你至多应该把 binlog 的格局设置为 mixed
双 M 架构
节点 A 和 B 之间总是互为主备关系。这样在切换的时候就不必再批改主备关系
如何解决循环复制的问题
- 规定两个库的 server id 必须不同,如果雷同,则它们之间不能设定为主备关系
- 一个备库接到 binlog 并在重放的过程中,生成与原 binlog 的 server id 雷同的新的 binlog
- 每个库在收到从本人的主库发过来的日志后,先判断 server id,如果跟本人的雷同,示意这个日志是本人生成的,就间接抛弃这个日志
主备提早
- 在备库上执行 show slave status 命令,它的返回后果外面会显示 seconds_behind_master,用于示意以后备库提早了多少秒
seconds_behind_master 的计算方法是这样的:
- 每个事务的 binlog 外面都有一个工夫字段,用于记录主库上写入的工夫;
- 备库取出以后正在执行的事务的工夫字段的值,计算它与以后零碎工夫的差值,失去 seconds_behind_master,单位是秒
备库连贯到主库的时候,会通过执行 SELECT UNIX_TIMESTAMP() 函数来取得以后主库的零碎工夫。如果这时候发现主库的零碎工夫与本人不统一,备库在执行 seconds_behind_master 计算的时候会主动扣掉这个差值
在网络失常的时候,日志从主库传给备库所需的工夫是很短的,网络失常状况下,主备提早的次要起源是备库接管完 binlog 和执行完这个事务之间的时间差。主备提早最间接的体现是,备库生产直达日志(relay log)的速度,比主库生产 binlog 的速度要慢。
主备提早的起因
- 备库所在机器的性能要比主库所在的机器性能差
- 备库的压力大。个别的想法是,主库既然提供了写能力,那么备库能够提供一些读能力。或者一些经营后盾须要的剖析语句,不能影响失常业务,所以只能在备库上跑。漠视了备库的压力管制。后果就是,备库上的查问消耗了大量的 CPU 资源,影响了同步速度,造成主备提早
解决形式:
- 一主多从。除了备库外,能够多接几个从库,让这些从库来分担读的压力。
- 通过 binlog 输入到内部零碎,比方 Hadoop 这类零碎,让内部零碎提供统计类查问的能力。
- 大事务:主库上必须等事务执行实现才会写入 binlog,再传给备库。所以,如果一个主库上的语句执行 10 分钟,那这个事务很可能就会导致从库提早 10 分钟
- 大表 DDL
主备切换形式
可靠性优先策略
- 判断备库 B 当初的 seconds_behind_master,如果小于某个值(比方 5 秒)持续下一步,否则继续重试这一步
- 把主库 A 改成只读状态,即把 readonly 设置为 true
- 判断备库 B 的 seconds_behind_master 的值,直到这个值变成 0 为止
- 把备库 B 改成可读写状态,也就是把 readonly 设置为 false
- 把业务申请切到备库 B
这个切换流程中是有不可用工夫的。因为在步骤 2 之后,主库 A 和备库 B 都处于 readonly 状态,也就是说这时零碎处于不可写状态,直到步骤 5 实现后能力复原
在这个不可用状态中,比拟消耗工夫的是步骤 3,可能须要消耗好几秒的工夫。这也是为什么须要在步骤 1 先做判断,确保 seconds_behind_master 的值足够小
可用性优先策略
不等主备数据同步,间接把连贯切到备库 B,并且让备库 B 能够读写,那么零碎简直就没有不可用工夫了
可用性优先策略,且 binlog_format=mixed
- 主库 A 执行完 insert 语句,插入了一行数据(4,4),之后开始进行主备切换。
- 因为主备之间有 5 秒的提早,所以备库 B 还没来得及利用“插入 c=4”这个直达日志,就开始接管客户端“插入 c=5”的命令。
- 备库 B 插入了一行数据(4,5),并且把这个 binlog 发给主库 A
- 备库 B 执行“插入 c=4”这个直达日志,插入了一行数据(5,4)。
- 而间接在备库 B 执行的“插入 c=5”这个语句,传到主库 A,就插入了一行新数据(5,5)。
- 最初的后果就是,主库 A 和备库 B 上呈现了两行不统一的数据。能够看到,这个数据不统一,是由可用性优先流程导致的。
可用性优先策略,但设置 binlog_format=row
因为 row 格局在记录 binlog 的时候,会记录新插入的行的所有字段值,所以最初只会有一行不统一。而且,两边的主备同步的利用线程会报错 duplicate key error 并进行。也就是说,这种状况下,备库 B 的 (5,4) 和主库 A 的 (5,5) 这两行数据,都不会被对方执行
总结:
- 应用 row 格局的 binlog 时,数据不统一的问题更容易被发现。
- 而应用 mixed 或者 statement 格局的 binlog 时,数据很可能悄悄地就不统一了。如果你过了很久才发现数据不统一的问题,很可能这时的数据不统一曾经不可查,或者连带造成了更多的数据逻辑不统一。
- 主备切换的可用性优先策略会导致数据不统一。因而,大多数状况下,倡议应用可靠性优先策略。毕竟对数据服务来说的话,数据的可靠性个别还是要优于可用性的。
并行复制
复制时,excecution 阶段能够并行执行,binlog flush 的时候,按程序进行;所以主节点和从节点的 binlog 还是统一的
mysql5.6
按库并行;同一个库的 binlog 由同一个 worker 解决,不同库的 binlog 由不同 worker 解决
mysql5.7
- DATABASE:默认值,基于库的并行复制形式
LOGICAL_CLOCK:基于组提交的并行复制形式。
- 一个组提交的事务都是能够并行回放,因为这些事务都已进入到事务的 Prepare 阶段,则阐明事务之间没有任何抵触(否则就不可能提交)
一个组提交的事务都是能够并行回放(配合 binary log group commit);slave 机器的 relay log 中 last_committed 雷同的事务(sequence_num 不同)能够并发执行
- sequence_number 是自增事务 ID,last_commited 代表上一个提交的事务 ID。
- 如果两个事务的 last_commited 雷同,阐明这两个事务是在同一个 Group 内提交的
mysql5.7.22
即便主库在串行提交的事务,只有相互不抵触,在备库就能够并行回放。
新增了一个参数 binlog-transaction-dependency-tracking,用来管制是否启用这个新策略。这个参数的可选值有以下三种
- COMMIT_ORDER,示意的就是后面介绍的,依据 5.7 的组提交策略
WRITESET,示意的是对于事务波及更新的每一行,计算出这一行的 hash 值,组成汇合 writeset。如果两个事务没有操作雷同的行,也就是说它们的 writeset 没有交加,就能够并行
- hash 值是通过“库名 + 表名 + 索引名 + 值 +(惟一索引、值)”计算出来的
- writeset 是在主库生成后间接写入到 binlog 外面的,从库执行时须要解析 binlog,判断 writeset 之间是否能够并行
- WRITESET 是一个 hash 数组,大小由参数 binlog_transaction_dependency_history_size 决定
- WRITESET_SESSION,在 WRITESET 形式的根底上,保障同一个 session 内的事务不可并行
主从切换
在传统的复制外面,当产生故障,须要主从切换,须要找到 Binlog 和 位点信息,复原实现数据之后将主节点指向新的主节点。在 MySQL 5.6 外面,提供了新的数据恢复思路,只须要晓得主节点的 IP、端口以及账号密码就行,因为复制是主动的,MySQL 会通过外部机制 GTID 主动找点同步
GTID (global transaction identifier)
全局事务 ID,一个事务对应一个 GTID,保障了在每个在主库上提交的事务在集群中有一个惟一的 ID
- GTID = source_id:transaction_id
source_id 失常即是 server_uuid,在第一次启动时生成(函数 generate_server_uuid),并长久化到 DATADIR/auto.cnf 文件里。
transaction_id 是程序化的序列号(sequence number),在每台 MySQL 服务器上都是从 1 开始自增长的序列,是事务的惟一标识。
GTID 的生成受参数 gtid_next 的值管制。
在 Master 上,gtid_next 默认是 AUTOMATIC, 即 GTID 在每次事务提交时主动生成。它从以后已执行的 GTID 汇合 (即 gtid_executed) 中,找一个大于 0 的未应用的最小值作为下个事务 GTID。在理论的更新事务记录之前将 GTID 写入到 Binlog。也能够设置 gtid_next 为一个指定的值。通过 set gtid_next=’current_gtid’指定为 current_gtid,那么就有两种可能:
- a. 如果 current_gtid 曾经存在于实例的 GTID 汇合中,接下来执行的这个事务会间接被零碎疏忽;
- b. 如果 current_gtid 没有存在于实例的 GTID 汇合中,就将这个 current_gtid 调配给接下来要执行的事务。
- 留神,一个 current_gtid 只能给一个事务应用。这个事务提交后,如果要执行下一个事务,就要执行 set 命令,把 gtid_next 设置成另外一个 gtid 或者 automatic
每个 MySQL 实例都保护了一个 GTID 汇合,用来对应“这个实例执行过的所有事务”- GTID 不便实现主从之间的 failover(主从切换),不必一步一步的去定位 Binlog 日志文件和查找 Binlog 的位点信息
- GTID 模式的启动也很简略,咱们只须要在启动一个 MySQL 实例的时候,加上参数 gtid_mode=on 和 enforce_gtid_consistency=on 就能够了
在 Slave 上,从 Binlog 先读取到主库的 GTID(即 set gtid_next 记录),而后执行的事务采纳该 GTID。
- 在原来基于日志的复制中,从库须要告知主库要从哪个偏移量进行增量同步,如果指定谬误会造成数据的脱漏,从而造成数据的不统一。
- 而基于 GTID 的复制中,从库会告知主库曾经执行的事务的 GTID 的值,而后主库会将所有未执行的事务的 GTID 的列表返回给从库,并且能够保障同一个事务只在指定的从库执行一次,通过全局的事务 ID 确定从库要执行的事务的形式代替了以前须要用 Binlog 和 位点确定从库要执行的事务的形式
步骤
- master 更新数据时,会在事务前产生 GTID,一起记录到 Binlog 日志中
- slave 端的 I/O 线程将变更的 Binlog,写入到本地的 relay log 中, 读取值是依据 gitd_next 变量,通知咱们 slave 下一个执行哪个 GTID
- SQL 线程从 relay log 中获取 GTID,而后比照 slave 端的 Binlog 是否有记录。如果有记录,阐明该 GTID 的事务曾经执行,slave 会疏忽
- 如果没有记录,slave 就会从 relay log 中执行该 GTID 的事务,并记录到 Binlog
从库切换主备步骤
A 是已经的主库,A‘是 A 的备库,它们互为主备;B 是从库,最开始它从 A 同步数据,当初须要从 A‘同步数据
- 把当初这个时刻,实例 A’的 GTID 汇合记为 set_a
- 实例 B 的 GTID 汇合记为 set_b
- 在实例 B 上执行 start slave 命令,实例 B 指定主库 A’,基于主备协定建设连贯
- 实例 B 把 set_b 发给主库 A’。
实例 A’算出 set_a 与 set_b 的差集,也就是所有存在于 set_a,然而不存在于 set_b 的 GTID 的汇合,判断 A’本地是否蕴含了这个差集须要的所有 binlog 事务
- 如果不蕴含,示意 A’曾经把实例 B 须要的 binlog 给删掉了,间接返回谬误;
- 如果确认全副蕴含,A’从本人的 binlog 文件外面,找出第一个不在 set_b 的事务,发给 B;之后就从这个事务开始,往后读文件,按程序取 binlog 发给 B 去执行
参考资料
极客工夫,mysql 实战 45 讲