MySQL 主从模式

本文将介绍 MySQL 中的主从模式。

一、主从模式

一个 MySQL 服务器可以配置为另外一个 MySQL 服务器的附属,将两者分别称为从库和主库,数据可以从主库中复制到从库中。

二、主从模式的好处

  • 实现了高可用灾备,当一个节点发生故障后可以切换另一个节点
  • 可以实现读写分离,避免一个数据库压力太大
  • 可以实现数据备份,每一个从库都是一个数据备份

三、binlog

主从模式基于 binlog,具体请看:

MySQL 三大日志 - binlog

四、主从复制的流程

  • 主库从本地读取 binlog,发送给从库
  • 从库拿到 binlog 后,通过 IO Thread 写入本地中转日志 relay log 中
  • 从库读取 relay log 中的内容,通过 SQL Thread 解析更新到数据库中

五、双主库结构

1. 单主库结构

假设有两台服务器,我们可以将其中一台设为主库,另外一台设为从库。

此时,如果需要切换主库,需要修改主库和从库的配置文件。

2. 双主库结构

(1) 双主库结构的好处

在实际生产中用得比较多的是双主库结构,两个 MySQL 服务器互为主从关系,这样在切换的时候只需要更改应用程序对主库的选择,而不需要再修改主从配置。

(2) 避免循环复制

你可能会想到这样一个问题:

在双主库结构下,

  • A 更新了一条语句,将 binlog 发送给从库 B
  • B 执行这条更新语句后也会生成 binlog,由于 B 也有从库 A,因此它也会将 binlog 发给 A
  • 那么 A 拿到这个 binlog 之后应该怎么做?两个库是否会发生循环复制?

MySQL 的解决方案是:

  • 规定有主从关系的两个库的 server id 必须不同
  • 在 binlog 中记录命令执行时所在实例的 server id
  • 从库在重放主库的 binlog 时,会生成 server id 为主库的日志
  • 从库在收到主库发过来的日志后,会先判断 server id,如果与自己的相同,则表示日志由自己生成,应该直接丢弃

(3) 双主库 != 双写

双主库不意味着我们可以同时向两个库写入内容, 应该严格保证同一时刻只有一个库作为主库,避免双写。

六、延迟

1. 什么是主从延迟?

与数据同步相关的时间点有以下三个:

  • 主库 A 执行命令,写入 binlog,记为 T1
  • binlog 被传给从库,记为 T2
  • 从库执行完成事务,记为 T3

所谓的主从延迟,就是同一个命令,在从库执行完成的时间和在主库完成的时间之差,即 T3 - T1。

时间之差的计算方法为:

  • binlog 中每条日志都有一个时间字段,用于记录主库写入的时间
  • 从库取出该时间字段,计算它与当前系统的差值,得到时间之差

2. 主从延迟的来源

  • 性能差距:主从库的机器性能存在差距

  • 从库压力过大:由于主库直接影响业务,因此大家使用时会比较克制,反而忽视了从库的压力控制。结果就是,大量的运营相关的分析语句在从库上执行,耗费了大量的 CPU 资源,影响了同步速度

  • 语句执行时间长:主库必须等语句执行完成后才会写入 binlog,再传给从库,如果主库中某个语句执行了 10 分钟,则这个语句可能导致从库延迟 10 分钟

  • 从库的复制能力不足:如果从库的复制能力低于主库的写入能力,从库会来不及 “消费” 主库的改动,造成巨大的主从延迟

    具体请看:七、并行复制

七、并行复制

1. 单线程复制

对于主库而言,MySQL 支持多个客户端同时连接,支持多个客户端同时执行命令,因此,主库支持并发写入;

对于从库而言,在 MySQL 5.6 之前,MySQL 只支持单线程复制。

因此,从库会来不及 “消费” 主库的改动,造成巨大的主从延迟。

2. 任务分配问题

如果要从单线程转向多线程,则 binlog 中的内容需要分配给多个线程执行,此时便需要考虑任务分配问题。

首先,任务分配不能以轮询的形式。

当任务被分配给多个线程之后,不同线程就会独立执行,由于 CPU 的调度策略,不同的线程的执行速度可能不同。

假设有两个任务先后更新同一行数据:

1
2
3
4
5
-- 任务 1
UPDATE table SET value = 1 WHERE ID = 1001

-- 任务 2
UPDATE table SET value = 2 WHERE ID = 1002

如果任务 1 分配给线程 1,任务 2 分配给线程 2,假设线程 2 先执行成功,线程 1 后执行成功,此时将会发生主从库不一致的情况。

其次,同一个事务的多个语句不能分配给不同的线程。

假设同一个事务的多个语句分配给不同的线程,当部分线程执行完成,部分线程未执行完成时,从库上将表现出 “事务执行一半” 的结果,破坏了事务的原子性。

因此,进行任务分配时,必须满足以下要求:

  • 不能造成更新覆盖,更新同一行的任务不能分发到多个线程
  • 事务不能拆开

3. 按表分发策略

如果两个任务更新同一个表,则将它们分配给同个线程顺序执行,否则并行执行。

4. 按行分发策略

如果两个任务更新同一行,则将它们分配给同个线程顺序执行,否则并行执行。

要求 binlog 的格式必须为 row

按行策略在决定线程如何分发时,需要消耗更多的计算资源

5. MySQL 5.6 的并行复制策略

MySQL 5.6 版本支持并行复制,但是支持的粒度是数据库,如果两个任务更新同一个数据库,则将它们分配给同个线程顺序执行,否则并行执行。

6. MySQL 5.7 的并行复制策略

略。

八、数据库选择

在实际开发中,配置主从模式的主要目的是读写分离,此时便涉及到数据库的选择,一般来说有以下两种做法:

  • 客户端直连:
    • 将数据库连接信息放在客户端,由客户端进行数据库的选择
    • 减少中间层,查询性能更好
    • 涉及主从切换、库迁移时,需要调整客户端中的数据库连接信息
  • 代理层:
    • 增加一个代理层,客户端连接代理层,由代理层根据请求类型和上下文决定请求的分发
    • 对于后端维护团队的要求会更高

九、过期读

1. 过期读

由于主从之间可能会存在延迟,可能会出现客户端执行操作后立马发起查询,无法从从库中获取到最新结果的情况,将这种 “从从库中读取到过期状态” 的情况称为过期读。

2. 过期读处理

  • 强制主库:对查询请求做分类,对于必须要拿到最新结果的请求,强制交给主库处理。
  • sleep(1):在执行更新请求之后,执行 sleep(1) 命令,再查询。这个方案的假设是,大多数情况下主从延迟在 1 秒内,sleep(1) 后再查询大概率拿到最新的数据。
  • 判断主从无延迟:通过 MySQL 的参数判断是否有主从延迟,当没有时进行查询
  • semi-sync
  • 等主库位点
  • GTID 方案

参考