当前位置: 首页 > article >正文

MySQL45讲 第二十八讲 读写分离有哪些坑?——阅读总结

文章目录

  • MySQL45讲 第二十八讲 读写分离有哪些坑?——阅读总结
    • 一、读写分离架构概述
      • (一)基本结构与实现方式
      • (二)读写分离面临的核心问题 - 过期读
    • 二、过期读问题的解决方案
      • (一)强制走主库方案
      • (二)Sleep 方案
      • (三)判断主备无延迟方案
      • (四)配合 semi - sync 方案
      • (五)等主库位点方案
      • (六)等 GTID 方案
    • 三、总结与思考

MySQL45讲 第二十八讲 读写分离有哪些坑?——阅读总结

在 MySQL 数据库架构中,读写分离是提升系统性能和扩展性的重要策略,尤其适用于读多写少的应用场景。然而,这一策略并非一帆风顺,其中最棘手的问题之一便是 “过期读”。今天,我们将深入探讨 MySQL 读写分离中的过期读现象,详细解析各种应对方案及其优缺点。

一、读写分离架构概述

(一)基本结构与实现方式

读写分离的基本结构通常包括一主多从架构,其中主库负责处理所有写入操作以及部分读操作,从库则分担主要的读请求。实现方式主要有两种:

  1. 客户端直连方案:客户端直接连接数据库,在客户端的连接层进行数据库连接信息的管理,并负责选择后端数据库进行查询。这种方案的优势在于查询性能稍好,架构简单,排查问题方便。但它也有缺点,例如客户端需要了解后端部署细节,在主备切换、库迁移等操作时,客户端需要感知并调整数据库连接信息。不过,一般会搭配如 Zookeeper 等负责管理后端的组件,尽量让业务端专注于业务逻辑开发。
  2. 带 proxy 的架构:在 MySQL 和客户端之间引入中间代理层 proxy,客户端仅连接 proxy,由 proxy 根据请求类型和上下文决定请求的分发路由。此架构对客户端友好,客户端无需关注后端细节,连接维护和后端信息维护等工作由 proxy 完成。然而,这对后端维护团队要求更高,且 proxy 需要具备高可用架构,整体架构相对复杂。目前,带 proxy 的架构呈现出逐渐流行的趋势。

(二)读写分离面临的核心问题 - 过期读

在读写分离架构中,由于主从之间可能存在延迟,当客户端执行完更新事务后立即发起查询,如果查询路由到从库,就有可能读取到事务更新之前的旧数据,这就是所谓的 “过期读” 现象。

尽管我们之前讨论了多种优化主备延迟的策略,但主从延迟仍无法完全避免,而客户端往往期望从库查询结果与主库一致,这就使得过期读问题成为读写分离架构中必须解决的关键挑战。


二、过期读问题的解决方案

(一)强制走主库方案

  1. 请求分类处理:将查询请求分为两类,一类是必须获取最新结果的请求,这类请求强制发送到主库,例如交易平台中卖家发布商品后立即查看商品是否发布成功的操作;另一类是可以接受读取旧数据的请求,可将其路由到从库,如买家浏览商铺页面,稍晚看到最新发布的商品是可接受的。
  2. 局限性此方案在实际应用中较为常用,但当遇到所有查询都不能是过期读的需求(如金融类业务)时,就不得不放弃读写分离,将所有读写压力集中在主库,这将严重影响系统的扩展性。

(二)Sleep 方案

  1. 基本思路:主库更新后,在读取从库数据之前先让程序休眠一段时间,例如执行 select sleep (1) 命令。其假设是大多数情况下主备延迟在 1 秒之内,通过休眠可增加获取最新数据的概率。
  2. 改进与问题:为改善用户体验,可采用类似 Ajax 的技术,在商品发布后,先将客户端输入内容作为 “新商品” 直接显示在页面上,而非立即查询数据库,待用户刷新页面时,已过一段时间,相当于实现了休眠目的,从而在一定程度上解决过期读问题。然而,该方案存在不精确性,一是即使查询在 0.5 秒内就能在从库获取正确结果,仍需等待 1 秒;二是若延迟超过 1 秒,过期读问题依然可能出现。

(三)判断主备无延迟方案

  1. 基于 seconds_behind_master 判断:通过检查 show slave status 结果中的 seconds_behind_master 参数是否为 0 来判断主备是否无延迟。若不为 0,则需等待该参数变为 0 后再执行查询请求。但该参数精度为秒,可能无法满足高精度需求。

  2. 对比位点确保无延迟:对比 Master_Log_File 和 Read_Master_Log_Pos(表示读到的主库最新位点)以及 Relay_Master_Log_File 和 Exec_Master_Log_Pos(表示备库执行的最新位点)。若这两组值完全相同,则表明接收到的日志已同步完成。

  3. 对比 GTID 集合确保无延迟:当 Auto_Position = 1 时,表示使用了 GTID 协议。此时,对比 Retrieved_Gtid_Set(备库收到的所有日志的 GTID 集合)和 Executed_Gtid_Set(备库已执行完成的 GTID 集合),若两个集合相同,也意味着备库接收到的日志均已同步完成。对比位点和对比 GTID 集合的方法比仅判断 seconds_behind_master 更为准确,但仍存在问题。即使备库认为已同步完成,仍可能存在部分日志处于客户端已收到提交确认,但备库尚未收到的状态,从而导致过期读。

    在这里插入图片描述

(四)配合 semi - sync 方案

  1. semi - sync 原理半同步复制(semi - sync replication)在事务提交时,**主库先将 binlog 发送给从库,从库收到后发回 ack 给主库,主库收到 ack 后才向客户端返回 “事务完成” 确认。**这样可确保发送过确认的事务,其日志已被备库接收。
  2. 局限性:在一主多从场景中,主库只需收到一个从库的 ack 就向客户端返回确认,此时若查询路由到未收到最新日志的从库,仍会产生过期读问题。此外,在业务更新高峰期,主库位点或 GTID 集合更新过快时,可能导致判断位点相等的条件一直不成立,从库迟迟无法响应查询请求,造成过度等待。

(五)等主库位点方案

  1. 关键命令与逻辑:使用 select master_pos_wait (file, pos [, timeout]) 命令,该命令在从库执行,**参数 file 和 pos 指定主库上的文件名和位置,timeout 为可选参数,设置等待超时时间。**命令正常返回表示从开始执行到应用完指定 binlog 位置所执行的事务数量;若执行期间备库同步线程异常则返回 NULL;若等待超时则返回 - 1;若刚开始执行时就发现已执行过该位置则返回 0。
  2. 执行流程:在事务更新完成后,执行show master status获取主库当前执行到的 File 和 Position;选定从库执行查询语句;在从库上执行 select master_pos_wait (File, Position, 1);若返回值大于等于 0,则在该从库执行查询语句,否则到主库执行查询语句。此方案在一定程度上解决了一主多从场景下的过期读问题,但如果所有从库都延迟超过设定时间,查询压力将全部转移到主库,可能导致主库压力过大,因此需要业务开发人员做好限流策略。

(六)等 GTID 方案

  1. 相关命令与流程:如果数据库开启了 GTID 模式,可使用 select wait_for_executed_gtid_set (gtid_set, 1) 命令。该命令等待直到库执行的事务包含传入的 gtid_set 后返回 0,超时则返回 1。执行流程为事务更新完成后,从返回包直接获取事务的 GTID(需将参数 session_track_gtids 设置为 OWN_GTID,并通过 API 接口 mysql_session_track_get_first 从返回包解析出 GTID 值);选定从库执行查询语句;在从库上执行 select wait_for_executed_gtid_set (gtid1, 1);若返回值为 0,则在该从库执行查询语句,否则到主库执行查询语句。同样,等待超时后是否转主库查询需业务开发人员考虑限流。
  2. 优势与问题:相比等主库位点方案,该方案在事务更新完成后无需再主动查询主库位点,减少了一次查询操作。但如果对主库大表进行 DDL 操作,可能导致主从延迟,进而使等 GTID 方案中的流量全部打到主库或全部超时

三、总结与思考

在 MySQL 的读写分离架构中,过期读问题是一个复杂且关键的挑战。我们探讨了多种解决方案,各有优劣,适用场景各异。强制走主库方案简单直接但可能牺牲扩展性;Sleep 方案虽能一定程度解决问题但不精确;判断主备无延迟方案存在理论上的漏洞;配合 semi - sync 方案在一主多从场景下不完全可靠;等主库位点和等 GTID 方案相对更完善,但在极端情况下仍可能面临主库压力过大的问题。在实际应用中,这些方案可以根据业务需求灵活组合使用,例如先对请求分类,再对不能接受过期读的语句采用等 GTID 或等位点方案。


http://www.kler.cn/a/404482.html

相关文章:

  • web钩子什么意思
  • Makefile 之 自动化变量
  • java: spire.pdf.free 9.12.3 create pdf
  • Linux:confluence8.5.9的部署(下载+安装+pojie)离线部署全流程 遇到的问题
  • Mybatis-Plus 多租户插件属性自动赋值
  • Docker是一个容器化平台注意事项
  • 第 24 章 -Golang 性能优化
  • 【C++入门(一)】半小时入门C++开发(深入理解new+List+范围for+可变参数)
  • 【GPTs】Front-end Expert:助力前端开发的智能工具
  • 设计模式之 组合模式
  • PCIe总线设计
  • Java中的TreeSet集合解析
  • 计算机毕设-基于springboot的多彩吉安红色旅游网站的设计与实现(附源码+lw+ppt+开题报告)
  • JMeter 性能测试计划深度解析:构建与配置的树形结构指南
  • k8s1.30.0高可用集群部署
  • 04-转录组下游分析-标准化、聚类、差异分析
  • C++真题实战(一)[卡片问题]
  • 动静态库:选择与应用的全方位指南
  • .NET开源实时应用监控系统:WatchDog
  • 《C++20 图形界面程序:速度与渲染效率的双重优化秘籍》
  • STM32学习笔记----三极管和MOS管的区别
  • 【人工智能】深度学习入门:用TensorFlow实现多层感知器(MLP)模型
  • 使用 IntelliJ IDEA 编写 Spark 应用程序(Scala + Maven)
  • 基于Spring AI alibaba组件AI问答功能开发示例
  • SpringBoot提交参数去除前后空格
  • Linux firewall防火墙规则