解决一个MYSQL的主从延迟问题

从一个主从延迟问题开始回顾主从复制原理,并思考主从延迟造成的原因和解决方案。当然,作为底层开发,最后还是只能快准狠的通过一个简单粗暴的等待方案进行应对。

事情的起因

事情要从我写下这样的代码开始

java 复制代码
// 获取当前数据库中未使用的数据转为正在使用的状态
int updateUsing = fateDataDao.update(FateDataStatusEnum.UNUSED.getCode(),FateDataStatusEnum.USING.getCode());
log.info("update UNUSED to USING:{}",updateUsing);
// 获取正在使用状态的数据
List<FateData> fateDataList = fateService.queryStatus(FateDataStatusEnum.USING.getCode());
log.info("queryStatus USING:{}",fateDataList.size());

这部分逻辑清晰简单明了,把UNUSED 状态的数据更新为USING 状态,然后查询取出USING状态的数据。

按照一个正常的逻辑来说updateUsing的数量和fateDataList.size()的数量应该一样,但是,他不正常

我在测试环境小数据量测试时,这段代码逻辑完全无误。但是上了灰度环境进行大量数据的测试就出现了这样的问题。

此时,我带着疑惑和不解,将目光投向百度。

首先我认为问题可能好似,MYSQL更新返回的是查询到的行数,而不是受影响的行数

Mybatis使用<update>标签怎么返回影响行数_mybatis返回影响行数-CSDN博客

但是负责MYSQL的同事和我说MYSQL已经配置了返回受影响行数,并告诉我应该是主从延迟问题,没办法解决,看看业务能不能改下吧。

这时,我才反应过来当时粗略了解的主从延迟问题,我已经忘的差不多了。

什么是主从复制?

要了解主从延迟,首先就要知道什么是主从复制。

MySQL的主从复制(Master-Slave Replication)是一种数据库复制技术,用于解决数据备份、读写分离、负载均衡以及故障恢复等问题。

主从复制的基本原理是将一个数据库实例(主服务器)的数据复制到另一个或多个数据库实例(从服务器),使得从服务器的数据与主服务器保持同步。

主从复制的基本工作流程:

  1. 从服务器连接到主服务器,生成两个线程,一个I/O线程,一个SQL线程
  2. 主服务器记录所有的数据更改(INSERT、UPDATE、DELETE),同时会生成一个 log dump 线程,用来给从库 i/o线程传binlog。
  3. 主服务器会生成一个 log dump 线程将binlog写到relay log(中继日志) 文件中
  4. 从服务器的SQL 线程会读取relay log文件中的日志,并解析成具体操作,来实现主从的操作一致,而最终数据一致;

流程图如下:

graph LR subgraph 主服务器 主库-->日志记录 日志记录-->|生成|日志转储线程[Log Dump] end subgraph 从服务器 从库-->I/O线程[I/O Thread] I/O线程-->SQL线程[SQL Thread] end 日志转储线程-->|写binlog|中继日志[Relay Log] 中继日志-->|读|I/O线程

主从复制解决的问题

要用到主从复制的原因主要是为了高可用、高并发

  1. 数据备份和恢复: 从服务器可以用作主服务器的备份,当主服务器发生故障时,可以快速切换到从服务器进行恢复。
  2. 读写分离: 主服务器负责写操作,而从服务器可以用于处理读操作,从而分担主服务器的负载,提高系统性能。
  3. 负载均衡: 多个从服务器可以平均分担读请求,实现负载均衡,提高系统的可伸缩性。
  4. 高可用性: 当主服务器故障时,可以快速切换到一个从服务器,保证系统的高可用性。

主从复制带来的问题

主从复制也会造成一些衍生出的问题:

  1. 数据一致性: 主从复制是异步的,存在一定的延迟,因此在进行读写分离时,需要注意可能出现的数据一致性问题。
  2. 写操作集中: 所有写操作都集中在主服务器上,可能导致主服务器的负载较高。
  3. 配置和维护: 操作复杂,需要正确配置主从服务器,以及定期进行监控和维护,确保系统正常运行。

本次遇到的bug,主要就是数据一致性方面的问题了。

主从延迟的原因

主库使用单线程顺序写入binlog,效率很高。然而从库的SQL Thread线程需要对主库的日志进行随机IO来重新执行DML和DDL,效率较低,难以跟上主库日志写入速度,因此产生了主从延迟。

另外,从库SQL Thread也是单线程,当主库并发较高时,产生大量DML,超过了从库单线程能处理的速度,或者从库中有大查询语句产生锁等待,也会导致从库执行延迟,无法跟上主库的进度

主从延迟的解决方案

从主从延迟的原因,我们定位出主要是主库的高并发和从库的SQL Thread效率低造成了这样的问题。

所以,在不增加机器的情况下的解决方案就是控制主库的并发 或者提升从库的SQL Thread处理效率,例如MySQL 5.6 版本后,提供的一种多线程的方式。

简单粗暴的解决方案

当然,对于我们公司的底层开发来说,这种层次的设计需要更高层面的人来推动,而且也需要更长的时间才能处理。

所以这里贴出我自己的解决方案。Thread.sleep时间请自行控制

java 复制代码
// 获取当前数据库中未使用的数据转为正在使用的状态
int updateUsing = fateDataDao.update(FateDataStatusEnum.UNUSED.getCode(),FateDataStatusEnum.USING.getCode());
LOGGER.info("update UNUSED to USING:{}",updateUsing);
// 获取正在使用状态的数据
List<FateData> fateDataList = fateService.queryStatus(FateDataStatusEnum.USING.getCode());
LOGGER.info("queryStatus USING:{}",fateDataList.size());
// 如果数据相等,直接略过。
if(updateUsing != fateDataList.size()){
    // updateUsing为0,但fateDataList不为空的情况。任务失败,未更新
    if (updateUsing == 0){
        Cat.logEvent("updateTmpData","jobFailed:"+"updateUsing:"+updateUsing+"--fateDataList:"+fateDataList.size());
        transaction.setStatus(Transaction.SUCCESS);
        return response;
    }
    // 数据数目不相等,等待三秒相等再继续
    boolean equalFlag = false;
    while(!equalFlag){
        // 等待主从延迟
        Thread.sleep(3000);
        fateDataList = fateService.queryStatus(FateDataStatusEnum.USING.getCode());
        Cat.logEvent("updateTmpData","equalFailed:"+"updateUsing:"+updateUsing+"--fateDataList:"+fateDataList.size());
        LOGGER.info("queryStatus USING:{}",fateDataList.size());
        equalFlag = true;
    }

    // 数据数目不相等,则需要数据不为空再继续
    while(fateDataList.isEmpty()){
        // 等待主从延迟
        Thread.sleep(1000);
        fateDataList = fateService.queryStatus(FateDataStatusEnum.USING.getCode());
        Cat.logEvent("updateTmpData","queryFailed:"+"updateUsing:"+updateUsing+"--fateDataList:"+fateDataList.size());
        LOGGER.info("queryStatus USING:{}",fateDataList.size());
    }
}

参考文章:

MySQL主从同步详解与配置 - 知乎 (zhihu.com)

从一个主从延迟问题,学习Mysql主从复制原理 - 掘金 (juejin.cn)

架构师必备:MySQL主从延迟解决办法 - 掘金 (juejin.cn)

(二十四)全解MySQL之主从篇:死磕主从复制中数据同步原理与优化 - 掘金 (juejin.cn)

相关推荐
打鱼又晒网2 小时前
【MySQL】数据库精细化讲解:内置函数知识穿透与深度学习解析
数据库·mysql
tatasix3 小时前
MySQL UPDATE语句执行链路解析
数据库·mysql
天海华兮3 小时前
mysql 去重 补全 取出重复 变量 函数 和存储过程
数据库·mysql
武子康5 小时前
大数据-231 离线数仓 - DWS 层、ADS 层的创建 Hive 执行脚本
java·大数据·数据仓库·hive·hadoop·mysql
黑色叉腰丶大魔王5 小时前
《MySQL 数据库备份与恢复》
mysql
Ljw...5 小时前
索引(MySQL)
数据库·mysql·索引
OpsEye5 小时前
MySQL 8.0.40版本自动升级异常的预警提示
数据库·mysql·数据库升级
Ljw...5 小时前
表的增删改查(MySQL)
数据库·后端·mysql·表的增删查改
i道i13 小时前
MySQL win安装 和 pymysql使用示例
数据库·mysql
Oak Zhang14 小时前
sharding-jdbc自定义分片算法,表对应关系存储在mysql中,缓存到redis或者本地
redis·mysql·缓存