1

如何避免MySQL主从延迟带来的读写问题?

 6 months ago
source link: https://www.51cto.com/article/783287.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

如何避免MySQL主从延迟带来的读写问题?

作者:蓝胖子的编程梦 2024-03-08 17:54:44
MYSQL中提供了一个函数select master_pos_wait(file, pos[, timeout])​ 用于在slave上执行等待master节点上的位点同步完成,其中file,和pos是在master上的文件和位点,timeout 为了让master_pos_wait 函数在timeout秒内没有返回,则会直接触发超时返回。

在MYSQL 部署架构选型上,许多公司都会用到主从读写分离的架构,如下是一个一主一从的架构,主库master负责写入,从库slave进行读取。

图片
图片

但是既然是读写分离,必然会面临这样一个问题,当在主库上进行更新后,有可能数据还没来得及同步到从库,但是这个时候又有读数据的需求,为了能正确读取出数据,这个时候就只有读主库了。但是这样做增加了主库的压力,违反了我们做读写分离的初衷。所以这一节我们就来针对这种情况探讨下,如何尽量的避免对主库的压力,尽量的从从库读取数据。

主从复制的原理

在探讨解决方案前,我们先要对主从复制的原理有所了解,数据库的操作都会记录到binlog,如下图所示,

图片
图片

1,从数据库(slave)会启动两个线程io_thread 和sql_thread ,通过io_thread将自身与主数据库(master)建立连接。

2,slave向master发出要同步的位置信息(包含同步的文件名和偏移量),表示需要从该位置发起同步。

3,主数据库master 将位置点后的binlog发送给slave,  slave获取到本地形成relay log(中转日志)。

4, 接着通过sql_thread解析relay log,执行sql。

从主从复制的过程可以看出,主从延迟时间是  在主库master执行sql的时间点到从库通过解析relay log 执行sql后的时间点之间的差值。如果应用程序能够在master写入数据后等待这么一段时间,再去slave读取,就能正确的读取出来数据了。

但是这个时间差值是不确定的,究竟应用程序需要等待多久才去读取slave,就成了我们需要思考🤔的问题。

如何避免延迟期间的主从数据不一致

比起在写入数据后读取主库或者写入数据后sleep一段时间读取从库,我给出两个我觉得比较靠谱点的方法。

判断位点是否同步

第一种方法是通过等待slave  将master写入数据后的 binlog的位点同步完成再对slave进行读取。

每次修改型sql的执行会将master的binlog 的位点(日志偏移量)前移,如果在修改型sql执行完成后,能够获取到master的binlog 位点,并且在客户端阻塞等待slave同步该位点完毕,再从slave读取就可以了。

MYSQL中提供了一个函数select master_pos_wait(file, pos[, timeout]) 用于在slave上执行等待master节点上的位点同步完成,其中file,和pos是在master上的文件和位点,timeout 为了让master_pos_wait 函数在timeout秒内没有返回,则会直接触发超时返回。

返回结果解析,

  • 返回结果正常情况下是一个大于0的整数,表示从pos位点开始完成了多少个事务。
  • 如果直接返回结果0,则说明在执行select master_pos_wait(file, pos[, timeout]) 时,位点已经同步完成。
  • 如果触发超时则返回-1。
  • 如果执行期间slave发生错误,则返回NULL。

所以,在判断是否应该在写入数据后读从库的逻辑,我们可以这样来写,

1, 在master写入数据后立马执行 show master status,可以获取如下结果

图片图片

Pasted image 20240308162704.png

可以看到master的binlog文件名称以及位点。

2, 在slave上执行 select master_pos_wait('mysql-bin.232011',3129472,1);,如果1s内没有返回,则直接返回-1。

图片
图片

3, 在上一步如果触发超时返回返回-1,则直接读取主库,如果是>=0 的值,则直接读取从库。

这样便能最大程度从从库读取数据。

判断GTID 是否同步

接着,我们来看下第二种方式,其实第二种方式和通过位点的方式类似,不同的是slave判断是否将数据同步完成的依据是看GTID的值。

什么是GTID值?

GTID 的全称是 Global Transaction Identifier,全局事务 ID,是一个事务在提交的时候生成的,是这个事务的唯一标识。

MYSQL开启 GTID 模式的方式是 在启动一个 MySQL 实例的时候,加上参数 gtid_mode=on 和 enforce_gtid_cnotallow=on 。

每个事务是和GTID 值一一对应的,每个MYSQL实例会维护一个GTID 集合,来表示实例执行过的事务。

在slave节点上,通过show slave status 可以看到 GTID集合,如下图所示,

图片
图片

Pasted image 20240308165622.png

  • Auto_Positinotallow=1 ,表示这对主备关系使用了 GTID 协议。
  • Retrieved_Gtid_Set,是备库收到的所有日志的 GTID 集合。
  • Executed_Gtid_Set,是备库所有已经执行完成的 GTID 集合。

如果Executed_Gtid_Set 等于Retrieved_Gtid_Set 说明slave将从master那里获取到的binlog全部执行完毕。

在master节点执行 show master status,也能看到GTID集合,Executed_Gtid_Set 为master节点执行过的GTID集合。如下图所示,

图片
图片

GTID 模式下判断同步的步骤

在GTID 模式下,从库slave从主库master取binlog的逻辑将不再是直接告诉master 要取的文件和位点了,而是由slave将自身的GTID集合告诉master。

master再结合自身的GTID集合,找出在master中有但是在slave中没有的GTID集合,然后从binlog中找到第一个不在GTID集合中的事务,从该事务的binlog位点开始,往后读取binlog发送给slave。

MYSQL针对于GTID同样提供 了一个函数select wait_for_executed_gtid_set(gtid_set, 1);  来让slave去判断对master执行过的gtid_set 是否已经同步完成。

wait_for_executed_gtid_set 函数的返回结果解析如下,

  • 如果slave 执行的事务中包含传入的 gtid_set,返回 0。
  • 如果等待1s后还没同步完成,则返回1。

所以在GTID 模式下的,在判断是否应该在写入数据后读从库的逻辑,我们可以这样来写,

1, 在master写入数据后立马执行 show master status,可以获取如下结果

图片图片

可以看到master的Executed_Gtid_Set的值。

2, 在slave上执行

select wait_for_executed_gtid_set('76cd5ea1-c541-11ee-87ef-fa163eefe144:1-56382789,  
808d2fb8-687b-11ec-b8b9-fa163e410530:1-144078103,  
9081c19b-63de-11ed-9755-fa163eb8b97f:1-1093294115', 1);

,如果1s内没有返回,则直接返回1。

图片图片

3, 在上一步如果触发超时即返回1,则直接读取主库,如果是=0 ,则直接读取从库。这样便能最大程度从从库读取数据。

责任编辑:武晓燕 来源: 蓝胖子的编程梦

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK