MySQL高可用,就这么完美???

2,709 阅读10分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

MySQL以其容易学习和高可用,被开发人员青睐。它的几乎所有的高可用架构,都直接依赖于 binlog。MySQL 能够成为现下最流行的开源数据库,binlog 功不可没。MySQL是怎样实现高可用的?这种高可用足够完美吗?

主备同步流程

流程

主库为A,备库为B,其同步流程如下图所示,这张图也很好的阐明一条更新语句,在master会执行哪些动作:

图片

备库 B 跟主库 A 之间维持了一个长连接。主库 A 内部有一个线程,专门用于服务备库 B 的这个长连接。一个事务日志同步的完整过程是这样的:

  1. 在备库 B 上通过 change master 命令,设置主库 A 的 IP、端口、用户名、密码,以及要从哪个位置开始请求 binlog,这个位置包含文件名和日志偏移量。

  2. 在备库 B 上执行 start slave 命令,这时候备库会启动两个线程,就是图中的 io_thread和 sql_thread。其中 io_thread 负责与主库建立连接。

  3. 主库 A 校验完用户名、密码后,开始按照备库 B 传过来的位置,从本地读取 binlog,发给 B。

  4. 备库 B 拿到 binlog 后,写到本地文件,称为中转日志(relay log)。

  5. sql_thread 读取中转日志,解析出日志里的命令,并执行。

同步位置

主备切换后,从库需要从新的主库同步数据。即上面流程第一步,需要指定从哪个位置开始请求binlog。主要有两种方案:

基于位点

MySQL5.6之前,使用change master命令更换主库。

CHANGE MASTER TO 

MASTER_HOST=$host_name 
MASTER_PORT=$port 
MASTER_USER=$user_name 
MASTER_PASSWORD=$password 
MASTER_LOG_FILE=$master_log_name 
MASTER_LOG_POS=$master_log_pos

操作流程如下:

  1. 等待新主库 A’把中转日志(relay log)全部同步完成;

  2. 在 A’上执行 show master status 命令,得到当前 A’上最新的 File 和 Position;

  3. 取原主库 A 故障的时刻 T;

  4. 用 mysqlbinlog 工具解析 A’的 File,得到 T 时刻的位点。

基于GTID

基于位点的方案太过繁琐,MySQL 5.6 版本引入了 GTID,无需人工计算位点。

GTID 的全称是 Global Transaction Identifier,也就是全局事务 ID,是一个事务在提交的时候生成的,是这个事务的唯一标识。每个 MySQL 实例都维护了一个 GTID 集合,用来对应“这个实例执行过的所有事务”。

CHANGE MASTER TO 
MASTER_HOST=$host_name 
MASTER_PORT=$port 
MASTER_USER=$user_name 
MASTER_PASSWORD=$password 
master_auto_position=1

master_auto_position=1 就表示这个主备关系使用的是 GTID 协议。

在实例 B 上执行 start slave 命令,取 binlog 的逻辑如下所示,其中set_a和set_b为执行过的事务的 GTID 集合:

  1. 实例 B 指定主库 A’,基于主备协议建立连接。

  2. 实例 B 把 set_b 发给主库 A’。

  3. 实例 A’算出 set_a 与 set_b 的差集,也就是所有存在于 set_a,但是不存在于 set_b的 GTID 的集合,判断 A’本地是否包含了这个差集需要的所有 binlog 事务。

    a. 如果不包含,表示 A’已经把实例 B 需要的 binlog 给删掉了,直接返回错误;

    b. 如果确认全部包含,A’从自己的 binlog 文件里面,找出第一个不在 set_b 的事务,发给 B;

  4. 之后就从这个事务开始,往后读文件,按顺序取 binlog 发给 B 去执行。

基于GTID的操作,可以认为是系统自行计算出对应位点。

循环问题

参数 log_slave_updates 设置为 on,表示备库执行 relay log 后生成 binlog。在主主复制+主从复制情况下,有时会发现主从没有同步,很可能是因为有的主库没有将log_slave_updates设置为on。

既然消费relay log会生成新的binlog,那双master情况下为何没有产生节点间循环复制情况?

主要是因为MySQL 在 binlog 中记录了这个命令第一次执行时所在实例的server id。

  1. 规定两个库的 server id 必须不同,如果相同,则它们之间不能设定为主备关系;

  2. 一个备库接到 binlog 并在重放的过程中,生成与原 binlog 的 server id 相同的新的binlog;

  3. 每个库在收到从自己的主库发过来的日志后,先判断 server id,如果跟自己的相同,表示这个日志是自己生成的,就直接丢弃这个日志。

高可用(HA)

现在大家都用MySQL,主要是因其高可用。高可用原因有两个,一个是主备一致,一个是主备切换。这两者缺一不可。

  1. 正常情况下,只要主库执行更新生成的所有 binlog,都可以传到备库并被正确地执行,备库就能达到跟主库一致的状态,这就是最终一致性。

  2. 主库出现问题,可以将备库作为主库,继续提供服务。

MySQL 高可用系统的基础,就是主备切换逻辑,但主备切换又很依赖主备延迟。

原因很容易理解,如果备库同步没有完成,此时将备库更改为主库,会产生数据丢失、数据不一致问题。

同步延迟

根据上面提到的主备同步流程,我们能够看出与数据同步有关的时间点主要包括以下三个:

  1. 主库 A 执行完成一个事务,写入 binlog,我们把这个时刻记为 T1;

  2. 之后传给备库 B,我们把备库 B 接收完这个 binlog 的时刻记为 T2;

  3. 备库 B 执行完成这个事务,我们把这个时刻记为 T3。

所谓主备延迟,就是同一个事务,在备库执行完成的时间和主库执行完成的时间之间的差值,也就是 T3-T1。所以主库和备库之间,必然会有延迟。

延迟时间可通过在备库上执行 show slave status 命令查看,它的返回结果里会显示seconds_behind_master,用于表示当前备库延迟了多少秒。DB监控上显示的时间,就是seconds_behind_master。

图片

延迟原因

主备延迟必然会有,但不应该延迟太长。主备延迟长,一般有如下几个原因:

  1. 备库所在机器的性能要比主库所在的机器性能差
  • 备库的机器配置本身就比主库差

  • 主库多机器部署,备库单机部署

  1. 备库的压力大
  • 备库上的查询耗费了大量的 CPU 资源
  1. 主库执行大事务或大表 DDL
  • 语句在主库执行多久,便会导致从库延迟多久
  1. 备库的并行复制能力
  • 备库使用单线程复制还是多线程复制

  • 从MySQL5.7.22开始,可以通过 binlog-transaction dependency-tracking 参数的 COMMIT_ORDER、WRITESET 和 WRITE_SESSION,选择使用哪种并行复制策略

主备切换

下面是双Master之间主备切换流程。一般说双M是指AB之间设置为互为主备,不过任何时刻只有一个节点在接受更新。

主备切换主要有两种策略,可靠性优先和可用性优先策略。

图片

可靠性优先

从状态 1 到状态 2 切换的详细过程是这样的:

  1. 判断备库 B 现在的 seconds_behind_master,如果小于某个值(比如 5 秒)继续下一步,否则持续重试;

  2. 把主库 A 改成只读状态,即把 readonly 设置为 true;

  3. 判断备库 B 的 seconds_behind_master 的值,直到这个值变成 0 为止;

  4. 把备库 B 改成可读写状态,也就是把 readonly 设置为 false;

  5. 把业务请求切到备库 B。

可靠性优先的好处是,主备的数据完全一致后再进行切换,不会引起系统问题。但缺点是系统有段时间不可用。

可用性优先

从状态 1 到状态 2 切换的详细过程是这样的:

  1. 把备库 B 改成可读写状态,也就是把 readonly 设置为 false;

  2. 把业务请求切到备库 B;

  3. 把主库 A 改成只读状态,即把 readonly 设置为 true;

可用性优先的好处是,系统几乎就没有不可用时间,坏处是系统可能出现数据不一致情况。

之所以出现数据不一致,是因为备库B接收业务请求的同时,还会继续消费未完成的binlog日志,新的请求和老的请求之间可能存在冲突。将binlog设置为row能够更加及时的发现这种问题,减少问题的加剧。

异常情况

假设,主库 A 和备库 B 间的主备延迟是 30 分钟,这时候主库 A 掉电了,HA 系统要切换B 作为主库。这时候切和不切都会有问题。

  • 切:有些数据在备库无法查到,而且会产生数据不一致问题

  • 不切:数据库不可使用

这也是为什么说MySQL 高可用系统的可用性,是依赖于主备延迟的。延迟的时间越小,在主库故障的时候,服务恢复需要的时间就越短,可用性就越高。所以无论是对DBA还是对研发人员而言,需要重点关注同步延迟。

问题

主从同步虽然给MySQL带来了高可用,但因为必然存在延迟问题,所以会导致更新完主库后,立即查从库,此时从库并没有更新后的数据。这个问题无法避免,但可以想办法优化,需要大家在付出和收益间做好权衡。

强制走主库方案

  • 查找操作不查从库,查主库

sleep 方案

  • 客户端更新成功后,过一小会再做查找操作

判断主备无延迟方案

  • 每次从库执行查询请求前,先判断seconds_behind_master 是否已经等于 0。如果还不等于 0 ,那就必须等到这个参数变为0 才能执行查询请求。

  • 判断位点:读到的主库的最新位点与备库执行的最新位点做比较,相等即可读

  • 判断GTID:备库收到的所有日志的 GTID 集合与备库所有已经执行完成的 GTID 集合是否一致,一致即可读

配合 semi-sync 方案

  • 一主一备下,半同步复制能确保主备全都收到了更新

等主库位点方案

  • 从库上执行select master_pos_wait(file, pos[, timeout]), 返回值是 >=0 的正整数则查询,其中参数 file 和 pos 指的是主库上的文件名和位置

等 GTID 方案

  • 从库上执行select wait_for_executed_gtid_set(gtid_set, 1),如果返回值是 0,则在这个从库执行查询语句,其中gtid_set是主库事务更新完成后,从返回包直接获取到事务的 GTID

总结

其实MySQL主从同步和开发人员相关性不大,但了解其中的不完美,对于发生异常时进行问题追查是很有帮助的。而且,能够扩展出许多新的玩法,如业务消费binlog日志,实现很多功能。

资料

  1. 主从同步设置的重要参数log_slave_updates

  2. MySQL45讲

  3. MySQL DDL--ghost工具学习

最后

大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)

我的个人博客为:shidawuhen.github.io/

往期文章回顾:

  1. 设计模式

  2. 招聘

  3. 思考

  4. 存储

  5. 算法系列

  6. 读书笔记

  7. 小工具

  8. 架构

  9. 网络

  10. Go语言