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

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工具学习

最后

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

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

图片

往期文章回顾:

  1. 设计模式

  2. 招聘

  3. 思考

  4. 存储

  5. 算法系列

  6. 读书笔记

  7. 小工具

  8. 架构

  9. 网络

  10. Go语言

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员麻辣烫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值