最近项目上线,遇到了主从问题。按理说公司基建不至于呈现这种问题,但便是呈现了。或许由于用的不是原生的MySQL吧。主从推迟会给前端和服务端带来很多问题,需求花费时刻用工程手法来处理,我以为这是很不合理的。

举几个由于主从推迟会导致问题场景:

  1. 创立了一个产品然后当即跳转到概况页
  2. 在列表页更新了用户的权限,当即刷新

凡是像这种操作后当即获取的,全会有问题。

为什么要有主从

MySQL数据库的主从(Master-Slave)架构首要是为了完结数据的高可用性(High Availability)和读写别离,详细的原因如下:

  1. 数据备份:主从架构可以完结数据的实时备份,从库可以作为主库的一个镜像存在,当主库呈现问题时,可以敏捷切换到从库,保证数据的安全性。
  2. 读写别离:在主从架构中,主库首要担任写操作,从库首要担任读操作,这样可以分管主库的压力,进步体系的处理才干。
  3. 故障切换:当主库呈现故障时,可以敏捷切换到从库,保证服务的连续性,进步体系的可用性。
  4. 负载均衡:经过主从架构,可以将读恳求涣散到多个从库,完结负载均衡,进步体系的功能。
  5. 数据一致性:主从仿制可以保证数据在主库和从库之间保持一致,进步数据的精确性。 因而,为了保证数据的安全性和体系的高可用性,MySQL通常会选用主从架构。

主从怎么同步

下面是一个update 句子在主节点 A 履行,然后同步到从节点 B 的完整流程图

主从推迟怎么处理

从库 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 读取中转日志,解析出日志里的指令,并履行。

为什么会主从推迟

什么是主从推迟?

  1. 主库 A 履行完结一个业务,写入 binlog,咱们把这个时刻记为 T1;

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

  3. 从库 B 履行完结这个业务,咱们把这个时刻记为 T3。

所谓主从推迟,便是同一个业务,在从库履行完结的时刻和主库履行完结的时刻之间的差值,也便是 T3-T1。

你可以在从库上履行 show slave status 指令,它的回来成果里面会显现seconds_behind_master,用于表明当时从库推迟了多少秒。

在网络正常的时分,日志从主库传给从库所需的时刻是很短的,即 T2-T1的值是十分小的。也便是说,网络正常状况下,主从推迟的首要来源是从库接收完 binlog和履行完这个业务之间的时刻差。

主从推迟来源

  1. 有些部署条件下,从库地点机器的功能要比主库地点的机器功能差。
  2. 从库的压力大。如从库上的查询耗费了很多的 CPU 资源,影响了同步速度,形成主从推迟。
  3. 大业务。由于主库上有必要等业务履行完结才会写入 binlog,再传给从库。所以,假如一个主库上的句子履行 10 分钟,那这个业务很或许就会导致从库推迟 10分钟。
  4. 大表 DDL,也是典型的大业务场景。
  5. 从库的并行仿制才干。查看软件版别,在官方的 5.6 版别之前,MySQL (sql_thread)只支持单线程仿制,由此在主库并发高、TPS 高时就会呈现严峻的主从推迟问题。

怎么处理

一般的主从结构如下:

主从推迟怎么处理

一旦呈现主从推迟问题,有如下处理计划

强制走主库计划 – 有点可行

  1. 将查询恳求做分类,有必要拿到最新成果的,强制恳求到主库
  • 长处:可以区分场景,压力可控
  • 缺陷:
    • 前后端都得改动。前端判别是否走主库,服务端判别指定场景走查主库
    • 后续保护也比较麻烦
    • 有时前端无法判别出场景,如进概况页,前端无法判别是刚创立完跳转的仍是打开的是早已创立的
  1. 悉数走主库
  • 长处:简略便捷
  • 缺陷:不区分详细场景,主库压力大
  1. 先读从库,从库没有读主库
  • 长处:相对简略
  • 缺陷:无法处理一切场景,如list的场景,由于必定有数据,但并不知道是否精确

sleep 计划 – 有点可行

  1. 前端推迟恳求
  • 长处:简略便捷
  • 缺陷:用户体验欠好
  1. 写相关接口,服务端回来成果概况,前端展示概况。如创立产品接口,服务端回来产品的详细信息,前端直接展示详细信息,不恳求接口
  • 长处:逻辑比较通顺、明晰
  • 缺陷:
    • 前端需求完结多套逻辑。如虽然是概况,但至少或许来自创立和概况接口;在成员列表中删去member成功后就直接不显现,不再调用list接口
    • 保护本钱高,如关于概况页,假如后续概况也变更,创立和概况接口怎么保持一致
    • 无法处理一切场景,如创立空间后获取成员列表(至少有自己)

判别主从推迟计划 – 有的可行

  1. 写操作写redis(过期时刻1~2s),读的时分判别是否有redis,有则读主库。如创立产品,则在redis里记载产品id,获取概况的时分先判别该产品id是否在redis存在,假如存在则读主库。
  • 长处:
    • 前端无需改动,服务端改动相对可控,而且设置为弱依赖,所以问题应该不大
    • 能处理大部分问题
  • 缺陷:
    • 服务端需求依据场景记载、读取redis
    • 有必定概率添加主库压力,但整体可控
  1. 判别主从无推迟计划 – 不可行
  • 每次从库履行查询恳求前,先判别seconds_behind_master 是否现已等于 0。假如还不等于 0 ,那就有必要比及这个参数变为0 才干履行查询恳求。可以使用 show slave status。
  • 对比位点保证主从无推迟
  • 对比 GTID 集合保证主从无推迟

这种方式感觉不太现实

  • 完结上本钱高
  • 依然有过期读问题。由于上面判别主从无推迟的逻辑,是“从库收到的日志都履行完结了”。可是,从 binlog在主从之间状态的剖析中,不难看出还有一部分日志,处于客户端现已收到提交承认,而从库还没收到日志的状态。
  • 假如在业务更新的高峰期,主库的位点或者 GTID 集合更新很快,那么上面的两个位点等值判别就会一向不建立,很或许呈现从库上迟迟无法呼应查询恳求的状况
  1. 配合 semi-sync(半同步仿制) 计划 – 不可行

主从无推迟计划 + semi-sync 计划 能处理过期读问题。

semi-sync 做了这样的规划:

  • 业务提交的时分,主库把 binlog 发给从库;

  • 从库收到 binlog 以后,发回给主库一个 ack,表明收到了;

  • 主库收到这个 ack 以后,才干给客户端回来“业务完结”的承认。

这样,semi-sync 配合前面关于位点的判别,就可以确认在从库上履行的查询恳求,可以防止过期读。

但这种计划也不太现实,而且没有完全处理问题

  • semi-sync+ 位点判别的计划,只对一主一从的场景是建立的。假如落到其它从库,仍是会呈现过期读
  1. 等主库位点计划 – 不可行
select master_pos_wait(file, pos[, timeout]);

这条指令的逻辑如下:

  • 它是在从库履行的;

  • 参数 file 和 pos 指的是主库上的文件名和方位;

  • timeout 可选,设置为正整数 N 表明这个函数最多等候 N 秒。

回来成果如下:

  • 假如履行期间,从库同步线程产生反常,则回来 NULL;

  • 假如等候超越 N 秒,就回来 -1;

  • 假如刚开端履行的时分,就发现现已履行过这个方位了,则回来 0。

  • 正常回来的成果是一个正整数 M,表明从指令开端履行,到应用完 file 和 pos 表明的 binlog 方位,履行了多少业务。

使用方法如下

  • trx1 业务在主库更新完结后,立刻履行 show master status 得到当时主库履行到的 File 和Position;

  • 选定一个从库履行查询句子;

  • 在从库上履行 select master_pos_wait(File, Position, 1);

  • 假如回来值是 >=0 的正整数,则在这个从库履行查询句子

这种也不太现实,想想完结复杂度有多高。

  1. 等 GTID 计划 – 不可行
 select wait_for_executed_gtid_set(gtid_set, 1);

这条指令的逻辑是:

  • 等候,直到这个库履行的业务中包括传入的 gtid_set,回来 0;

  • 超时回来 1。

等 GTID 的履行流程为:

  • trx1 业务更新完结后,从回来包直接获取这个业务的 GTID,记为 gtid1;

  • 选定一个从库履行查询句子;

  • 在从库上履行 select wait_for_executed_gtid_set(gtid1, 1);

  • 假如回来值是 0,则在这个从库履行查询句子;

  • 否则,到主库履行查询句子。

总结

真的是基建要做好,基建好了大家能把精力放到更重要的工作上。假如真呈现主从推迟问题,能选择的计划其实比较少。要么就分场景走主库、要么前端sleep(偏暂时计划)、要目服务端自己判别一下主从推迟状况。

这次咱们选择redis打点记载,看看效果怎么样吧。

最终

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

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

往期文章回忆:

  1. 规划形式
  2. 招聘
  3. 思考
  4. 存储
  5. 算法系列
  6. 读书笔记
  7. 小工具
  8. 架构
  9. 网络
  10. Go言语