一篇文带你彻底解决mysql的主从复制延迟问题


1、在某些部署环境中,备库所在的机器性能要比主库所在的机器性能差。此时如果机器的资源不足的话就会影响备库同步的效率;

2、备库充当了读库,一般情况下主要写的压力在于主库,那么备库会提供一部分读的压力,而如果备库的查询压力过大的话,备库的查询消耗了大量的CPU资源,那么必不可少的就会影响同步的速度

3、大事务执行,如果主库的一个事务执行了10分钟,而binlog的写入必须要等待事务完成之后,才会传入备库,那么此时在开始执行的时候就已经延迟了10分钟了

4、主库的写操作是顺序写binlog,从库单线程去主库顺序读binlog,从库取到binlog之后在本地执行。mysql的主从复制都是单线程的操作,但是由于主库是顺序写,所以效率很高,而从库也是顺序读取主库的日志,此时的效率也是比较高的,但是当数据拉取回来之后变成了随机的操作,而不是顺序的,所以此时成本会提高。

5、 从库在同步数据的同时,可能跟其他查询的线程发生锁抢占的情况,此时也会发生延时。

6、 当主库的TPS并发非常高的时候,产生的DDL数量超过了一个线程所能承受的范围的时候,那么也可能带来延迟

7、 在进行binlog日志传输的时候,如果网络带宽也不是很好,那么网络延迟也可能造成数据同步延迟

这些就是可能会造成备库延迟的原因

3、如何解决复制延迟的问题


先说一些虚的东西,什么叫虚的东西呢?就是一听上去感觉很有道理,但是在实施或者实际的业务场景中可能难度很大或者很难实现,下面我们从几个方面来进行描述:

1、架构方面


1、业务的持久化层的实现采用分库架构,让不同的业务请求分散到不同的数据库服务上,分散单台机器的压力

2、服务的基础架构在业务和mysql之间加入缓存层,减少mysql的读的压力,但是需要注意的是,如果数据经常要发生修改,那么这种设计是不合理的,因为需要频繁地去更新缓存中的数据,保持数据的一致性,导致缓存的命中率很低,所以此时就要慎用缓存了

3、使用更好的硬件设备,比如cpu,ssd等,但是这种方案一般对于公司而言不太能接受,原因也很简单,会增加公司的成本,而一般公司其实都很抠门,所以意义也不大,但是你要知道这也是解决问题的一个方法,只不过你需要评估的是投入产出比而已

2、从库配置方面


1、修改sync_binlog的参数的值

想要合理设置此参数的值必须要清楚地知道binlog的写盘的流程:

image

可以看到,每个线程有自己的binlog cache,但是共用同一份binlog。

图中的write,指的就是把日志写入到文件系统的page cache,并没有把数据持久化到磁盘,所以速度快

图中的fsync,才是将数据持久化到磁盘的操作。一般情况下,我们认为fsync才占用磁盘的IOPS

而write和fsync的时机就是由参数sync_binlog来进行控制的。

1、当sync_binlog=0的时候,表示每次提交事务都只write,不fsync

2、当sync_binlog=1的时候,表示每次提交事务都执行fsync

3、当sync_binlog=N的时候,表示每次提交事务都write,但积累N个事务后才fsync。

一般在公司的大部分应用场景中,我们建议将此参数的值设置为1,因为这样的话能够保证数据的安全性,但是如果出现主从复制的延迟问题,可以考虑将此值设置为100~1000中的某个数值,非常不建议设置为0,因为设置为0的时候没有办法控制丢失日志的数据量,但是如果是对安全性要求比较高的业务系统,这个参数产生的意义就不是那么大了。

2、直接禁用salve上的binlog,当从库的数据在做同步的时候,有可能从库的binlog也会进行记录,此时的话肯定也会消耗io的资源,因此可以考虑将其关闭,但是需要注意,如果你搭建的集群是级联的模式的话,那么此时的binlog也会发送到另外一台从库里方便进行数据同步,此时的话,这个配置项也不会起到太大的作用

3、设置innodb_flush_log_at_trx_commit 属性,这个属性在我讲日志的时候讲过,用来表示每一次的事务提交是否需要把日志都写入磁盘,这是很浪费时间的,一共有三个属性值,分别是0(每次写到服务缓存,一秒钟刷写一次),1(每次事务提交都刷写一次磁盘),2(每次写到os缓存,一秒钟刷写一次),一般情况下我们推荐设置成2,这样就算mysql的服务宕机了,卸载os缓存中的数据也会进行持久化。

4、从根本上解决主从复制的延迟问题


很多同学在自己线上的业务系统中都使用了mysql的主从复制,但是大家需要注意的是,并不是所有的场景都适合主从复制,一般情况下是读要远远多于写的应用,同时读的时效性要求不那么高的场景。如果真实场景中真的要求立马读取到更新之后的数据,那么就只能强制读取主库的数据,所以在进行实现的时候要考虑实际的应用场景,不要为了技术而技术,这是很严重的事情。

在mysql5.6版本之后引入了一个概念,就是我们通常说的并行复制,如下图:

image

通过上图我们可以发现其实所谓的并行复制,就是在中间添加了一个分发的环节,也就是说原来的sql_thread变成了现在的coordinator组件,当日志来了之后,coordinator负责读取日志信息以及分发事务,真正的日志执行的过程是放在了worker线程上,由多个线程并行的去执行。

– 查看并行的slave的线程的个数,默认是0.表示单线程

show global variables like ‘slave_parallel_workers’;

– 根据实际情况保证开启多少线程

set global slave_parallel_workers = 4;

– 设置并发复制的方式,默认是一个线程处理一个库,值为database

show global variables like ‘%slave_parallel_type%’;

– 停止slave

stop slave;

– 设置属性值

set global slave_parallel_type=‘logical_check’;

– 开启slave

start slave

– 查看线程数

show full processlist;

通过上述的配置可以完成我们说的并行复制,但是此时你需要思考几个问题

1、在并行操作的时候,可能会有并发的事务问题,我们的备库在执行的时候可以按照轮训的方式发送给各个worker吗?

答案是不行的,因为事务被分发给worker以后,不同的worker就开始独立执行了,但是,由于CPU的不同调度策略,很可能第二个事务最终比第一个事务先执行,而如果刚刚好他们修改的是同一行数据,那么因为执行顺序的问题,可能导致主备的数据不一致

2、同一个事务的多个更新语句,能不能分给不同的worker来执行呢?

答案是也不行,举个例子,一个事务更新了表t1和表t2中的各一行,如果这两条更新语句被分到不同worker的话,虽然最终的结果是主备一致的,但如果表t1执行完成的瞬间,备库上有一个查询,就会看到这个事务更新了一半的结果,破坏了事务逻辑的隔离性。

我们通过讲解上述两个问题的最主要目的是为了说明一件事,就是coordinator在进行分发的时候,需要遵循的策略是什么?

1、不能造成更新覆盖。这就要求更新同一行的两个事务,必须被分发到同一个worker中。

2、同一个事务不能被拆开,必须放到同一个worker中。

听完上面的描述,我们来说一下具体实现的原理和过程

如果让我们自己来设计的话,我们应该如何操作呢?这是一个值得思考的问题。其实如果按照实际的操作的话,我们可以按照粒度进行分类,分为按库分发,按表分发,按行分发。

其实不管按照什么方式进行分发,大家需要注意的就是在分发的时候必须要满足我们上面说的两条规则,所以当我们进行分发的时候要在每一个worker上定义一个hash表,用来保存当前这个work正在执行的事务所涉及到的表。hash表的key值按照不同的粒度需要存储不同的值:

按库分发:key值是数据库的名字,这个比较简单

按表分发:key值是库名+表名

按行分发:key值是库名+表名+唯一键

1、MySQL5.6版本的并行复制策略


其实从mysql的5.6版本开始就已经支持了并行复制,只是支持的粒度是按库并行,这也是为什么现在的版本中可以选择类型为database,其实说的就是支持按照库进行并行复制。

但是其实用过的同学应该都知道,这个策略的并行效果,取决于压力模型。如果在主库上有多个DB,并且各个DB的压力均衡,使用这个策略的效果会很好,但是如果主库的所有表都放在同一DB上,那么所有的操作都会分发给一个worker,变成单线程操作了,那么这个策略的效果就不好了,因此在实际的生产环境中,用的并不是特别多

2、mariaDB的并行复制策略


在mysql5.7的时候采用的是基于组提交的并行复制,换句话说,slave服务器的回放与主机是一致的,即主库是如何并行执行的那么slave就如何怎样进行并行回放,这点其实是参考了mariaDB的并行复制,下面我们来看下其实现原理。

mariaDB的并行复制策略利用的就是这个特性:

1、能够在同一组里提交的事务,一定不会修改同一行;

2、主库上可以并行执行的事务,备库上也一定是可以并行执行的。

在实现上,mariaDB是这么做的:

1、在一组里面一起提交的事务,有一个相同的commit_id,下一组就是commit_id+1;

2、commit_id直接写到binlog里面;

3、传到备库应用的时候,相同commit_id的事务会分发到多个worker执行;

4、这一组全部执行完成后,coordinator再去取下一批。

这是mariaDB的并行复制策略,大体上看起来是没有问题的,但是你仔细观察的话会发现他并没有实现“真正的模拟主库并发度”这个目标,在主库上,一组事务在commit的时候,下一组事务是同时处于“执行中”状态的。

我们真正想要达到的并行复制应该是如下的状态,也就是说当第一组事务提交的是,下一组事务是运行的状态,当第一组事务提交完成之后,下一组事务会立刻变成commit状态

image

但是按照mariaDB的并行复制策略,那么备库上的执行状态会变成如下所示:

image

可以看到,这张图跟上面这张图的最大区别在于,备库上执行的时候必须要等第一组事务执行完成之后,第二组事务才能开始执行,这样系统的吞吐量就不够了。而且这个方案很容易被大事务拖后腿,如果trx2是一个大事务,那么在备库应用的时候,trx1和trx3执行完成之后,就只能等trx2完全执行完成,下一组才能开始执行,这段时间,只有一个worker线程在工作,是对资源的浪费。

3、mysql5.7的并行复制策略


mysql5.7版本的时候,根据mariaDB的并行复制策略,做了相应的优化调整后,提供了自己的并行复制策略,并且可以通过参数slave-parallel-type来控制并行复制的策略:

1、当配置的值为DATABASE的时候,表示使用5.6版本的按库并行策略;

2、当配置的值为LOGICAL_CLOCK的时候,表示跟mariaDB相同的策略。

此时,大家需要思考一个问题:同时处于执行状态的所有事务,是否可以并行?

答案是不行的,因为多个执行中的事务是有可能出现锁冲突的,锁冲突之后就会产生锁等待问题。

在mariaDB中,所有处于commit状态的事务是可以并行,因为如果能commit的话就说明已经没有锁的问题,但是大家回想下,我们mysql的日志提交是两阶段提交,如下图,其实只要处于prepare状态就已经表示没有锁的问题了。

image

因此,mysql5.7的并行复制策略的思想是:

1、同时处于prepare状态的事务,在备库执行是可以并行的。

2、处于prepare状态的事务,与处于commit状态的事务之间,在备库上执行也是可以并行的。

基于这样的处理机制,我们可以将大部分的日志处于prepare状态,因此可以设置

1、binlog_group_commit_sync_delay 参数,表示延迟多少微秒后才调用 fsync;

2、binlog_group_commit_sync_no_delay_count 参数,表示累积多少次以后才调用 fsync。

5、基于GTID的主从复制问题


在我们之前讲解的主从复制实操中,每次想要复制,必须要在备机上执行对应的命令,如下所示:

change master to master_host=‘192.168.85.11’,master_user=‘root’,master_password=‘123456’,master_port=3306,master_log_file=‘master-bin.000001’,master_log_pos=154;

在此配置中我们必须要知道具体的binlog是哪个文件,同时在文件的哪个位置开始复制,正常情况下也没有问题,但是如果是一个主备主从集群,那么如果主机宕机,当从机开始工作的时候,那么备机就要同步从机的位置,此时位置可能跟主机的位置是不同的,因此在这种情况下,再去找位置就会比较麻烦,所以在5.6版本之后出来一个基于GTID的主从复制。

GTID(global transaction id)是对于一个已提交事务的编号,并且是一个全局唯一的编号。GTID实际上是由UUID+TID组成的,其中UUID是mysql实例的唯一标识,TID表示该实例上已经提交的事务数量,并且随着事务提交单调递增。这种方式保证事务在集群中有唯一的ID,强化了主备一致及故障恢复能力

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

各位读者,由于本篇幅度过长,为了避免影响阅读体验,下面我就大概概括了整理了

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
img.cn/images/e5c14a7895254671a72faed303032d36.jpg" alt=“img” style=“zoom: 33%;” />

最后

各位读者,由于本篇幅度过长,为了避免影响阅读体验,下面我就大概概括了整理了

[外链图片转存中…(img-5uXZlKUG-1713437080081)]

[外链图片转存中…(img-tgiLJQzG-1713437080082)]

[外链图片转存中…(img-JhyPhp3f-1713437080082)]

[外链图片转存中…(img-qsSL0OT2-1713437080082)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值