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

2024年 2月 26日 125.0k 0

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

事情的起因

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

// 获取当前数据库中未使用的数据转为正在使用的状态
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)是一种数据库复制技术,用于解决数据备份、读写分离、负载均衡以及故障恢复等问题。

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

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

  • 从服务器连接到主服务器,生成两个线程,一个I/O线程,一个SQL线程
  • 主服务器记录所有的数据更改(INSERT、UPDATE、DELETE),同时会生成一个 log dump 线程,用来给从库 i/o线程传binlog。
  • 主服务器会生成一个 log dump 线程将binlog写到relay log(中继日志) 文件中
  • 从服务器的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线程
    

    主从复制解决的问题

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

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

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

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

    主从延迟的原因

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

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

    主从延迟的解决方案

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

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

    简单粗暴的解决方案

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

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

    // 获取当前数据库中未使用的数据转为正在使用的状态
    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)

    相关文章

    Oracle如何使用授予和撤销权限的语法和示例
    Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
    下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
    社区版oceanbase安装
    Oracle 导出CSV工具-sqluldr2
    ETL数据集成丨快速将MySQL数据迁移至Doris数据库

    发布评论