【MySQL】MySQL 缓存方案

发布于:2025-07-27 ⋅ 阅读:(12) ⋅ 点赞:(0)

一、MySQL主从同步

1.1 主从同步是什么

  • MySQL 主从同步是一种数据复制机制,通过该机制可以实现将主数据库(Master)的 DDL(数据定义语言)和 DML(数据操纵语言,如 update、insert、delete)等操作同步到从数据库(Slave),从而保证主从数据库的数据一致性。

  • 这种机制在提高数据可用性、实现读写分离、进行数据备份与恢复等场景中发挥着重要作用。

1.2 主从同步原理

1.2.1 主从同步的组件

主从同步的实现依赖以下的组件:

  • binlog(二进制日志):主库产生的日志文件,记录了主库上所有的数据变更操作(如创建表、插入数据等),是主从同步的基础数据来源。

  • relay-log(中继日志):从库上的日志文件,用于存储从主库的 binlog 中复制过来的日志内容,相当于一个中间过渡的日志。

  • io-thread(IO 线程)

    • 主库的 IO 线程:负责接收从库 IO 线程的请求,并将 binlog 中指定位置后的日志内容发送给从库。
    • 从库的 IO 线程:负责连接主库,请求并接收主库发送的 binlog 日志,然后将其写入从库的 relay-log。
  • sql-thread(SQL 线程):从库上的线程,负责读取 relay-log 中的内容,并解析成具体的 SQL 语句在从库上执行,实现数据的重放(replay),从而保证从库与主库数据一致。

  • master-info 文件:从库上的文件,记录了已读取到的主库 binlog 的文件名和位置,方便从库下次请求主库时明确起始读取点。

1.2.2 主从同步的流程

主从同步的流程可以分为三部分:

  1. 主库生成binlog
  2. 从库获取binlog并写入relay-log
  3. 从库重放中继日志
主库生成binlog

主库执行 DML(如 update、insert、delete)或 DDL 操作后,会将这些操作记录到自身的 binlog 中,完成数据变更的日志化存储。

从库获取binlog并写入relay-log
  1. 从库的 IO 线程主动连接主库,并请求从指定 binlog 文件的指定位置(首次可从最开始)之后的日志内容。

  2. 主库接收到请求后,其负责复制的 IO 线程会根据请求的位置,读取对应 binlog 中的日志信息,然后返回给从库的 IO 线程。返回的信息除了日志内容,还包括当前主库 binlog 的文件名和位置。

  3. 从库的 IO 线程收到信息后,将日志内容追加到从库的 relay-log 末尾,同时将主库的 binlog 文件名和位置记录到 master-info 文件中,为下一次请求提供起始点。

从库重放中继日志

从库的 SQL 线程实时监测 relay-log,当发现有新增内容时,会解析这些内容,还原成主库上执行过的具体 SQL 语句,并在从库上执行这些语句,从而实现数据同步。
在这里插入图片描述

1.3 读写分离

MySQL 读写分离是基于主从同步机制实现的数据库架构优化方案,其核心思想是将数据库的写操作(如 INSERT、UPDATE、DELETE)集中在主库(Master)处理,而读操作(如 SELECT)分散到从库(Slave)执行,从而有效分担主库压力、提升系统整体性能。

在这里插入图片描述

1.3.1 读写分离的优点

  1. 减轻主库负载:主库仅处理写操作和必要的核心读操作,避免因大量读请求占用资源(如 CPU、IO)而影响写操作效率。

  2. 提高读操作吞吐量:通过多个从库分担读请求,利用分布式部署的优势提升系统整体的读性能,支持更多并发查询。

  3. 提升系统可用性:即使主库故障,从库仍可提供读服务,减少业务中断时间。

  4. 支持业务扩展:可根据读请求压力灵活增加从库数量,实现横向扩展,而无需修改核心业务逻辑。

1.3.2 读写分离的缺点

  1. 数据不一致:主从同步依赖 binlog 复制和 SQL 重放,从库数据通常滞后于主库,若写操作后立即从从库读取,可能获取旧数据,导致数据不一致

1.3.3 读写分离的适用场景

  • 读多写少业务:如电商商品列表、新闻资讯、社交平台动态等,读请求量远大于写请求。

  • 高并发查询场景:单库读性能无法满足并发需求(如秒杀活动中的商品库存查询),需通过从库分担压力。

  • 非核心写操作场景:写操作频率低(如后台数据录入),主库压力小,适合通过从库扩展读能力。

1.4 MySQL缓冲层

在 “读多写少、以 MySQL 为核心数据源、Redis 为缓存层” 的场景中,基于 MySQL 与 Redis 的缓存策略核是利用 Redis 的内存级读写速度提升读性能,同时通过合理的同步机制保证缓存与 MySQL 的数据一致性,避免因缓存引入脏数据或业务异常

1.4.1 为什么需要引入Redis

  • MySQL也有缓冲层,它的作用是用来缓存热点数据,这些数据包括索引、记录等,mysql 缓冲层是从自身出发,跟具体的业务无关,缓冲策略主要是 LRU,由于 mysql 的缓冲层(buffer pool)不由用户来控制,也就不能由用户来控制缓存具体数据

  • MySQL 数据主要存储在磁盘当中,适合大量重要数据的存储。磁盘当中的数据一般是远大于内存当中的数据,一般业务场景关系型数据库(mysql)作为主要数据库;

  • Redis是内存数据库,它的所有数据都存储在内存当中,当然也可以持久化到磁盘中,因此它的速度很快,很适合作为MySQL的缓存,存储与业务相关的热点数据,这些热点数据可以由用户自己定义

在这里插入图片描述

1.4.2 MySQL和Redis的同步问题

我们这里讨论的数据基于用户定义的热点数据,当引入 Redis 后,缓存与 MySQL 的数据可能存在 5 种状态:

  1. MySQL有,缓存无(正常)
  2. MySQL有,缓存有(正常)
  3. 二者都有,数据一致(正常)
  4. MySQL无,缓存有(不正常)
  5. 二者都有,数据不一致(不正常)
  • 需要明确的是,我们获取数据主要依据MySQL为主,如果缓存中不存在,那么我们只需要将MySQL中的数据同步到缓存即可,没有什么大问题

  • 如果缓存有,但是MySQL没有,这样就会产生脏数据

  • 如果MySQL和缓存都有这个数据,但是数据不一致,这样也会有问题

1.4.3 同步问题解决方案

解决方案1

读数据:

  • 先从缓存获取,如果有直接返回
  • 如果没有,从MySQL中获取
    • 如果MySQL有,同步到缓存并返回
    • 如果MySQL没有,返回空

作用

  • 通过 “缓存未命中时从 MySQL 加载并同步”,保证缓存数据最终与 MySQL 一致

写数据:

  • 先删除缓存,再写MySQL,后续数据同步使用中间件go-mysql-transfer等中间件处理

作用

  • 避免 “缓存与 MySQL 数据不一致”:删除缓存后,写 MySQL 期间缓存为空,其他服务读时会直接查 MySQL,拿到最新数据并同步到缓存
  • 保证 “写后立即读” 的正确性:服务 A 写完 MySQL 后立即读数据时,因缓存已被删除,读流程会查 MySQL 拿到最新数据

问题:

  • 中间件同步延迟:若中间件同步缓存的过程延迟,缓存会在短时间内为空,此时读请求全部走 MySQL,可能短暂增加 MySQL 压力
解决方案2

读数据:

  • 先从缓存获取,如果有直接返回
  • 如果没有,从MySQL中获取
    • 如果MySQL有,同步到缓存并返回
    • 如果MySQL没有,返回空

作用:读数据的流程和解决方案1完全一致

写数据:

  • 先写缓存,并设置过期时间(如200ms),再写MySQL
  • 后续数据同步使用中间件实现

作用

  • 减少缓存空窗期:写操作后 Redis 立即有新数据(而非像策略 1 那样先删除),短时间内读请求可直接从 Redis 获取,减轻 MySQL 压力。

  • 控制脏数据时长:即使 MySQL 写入失败,脏数据仅存在 200ms,过期后缓存失效,读请求会查 MySQL 拿到正确状态

问题

  • 短暂脏数据:若 MySQL 写入失败,Redis 中 200ms 内的新数据是 “假数据”,可能导致用户看到错误信息

在这里插入图片描述

1.4.3 其他缓存问题

在 MySQL 与 Redis 组成的缓存架构中,由于数据同步策略、并发请求及缓存特性等因素,可能出现缓存穿透、缓存击穿、缓存雪崩三种典型问题

1.4.3.1 缓存穿透

当请求查询的数据在 Redis 中不存在,且在 MySQL 中也不存在时,该请求会绕过 Redis 直接穿透到MySQL,若此类请求大量并发,会导致 MySQL 压力剧增,甚至崩溃

原因:请求的是 “不存在的数据”,缓存(Redis)和数据库(MySQL)均无记录,导致每次请求都直接访问
数据库

解决方案

  1. 缓存空值(<key, nil>)

    • 当 MySQL 中查询到数据不存在时,在 Redis 中缓存该 key 对应的空值(如 nil),并设置较短的过期时间(如几分钟)。
    • 下次相同请求会直接从 Redis 获取空值,避免穿透到 MySQL。
  2. 布隆过滤器

  • 预先将 MySQL 中所有存在的 key 存入布隆过滤器(一种高效的概率性数据结构)。
  • 请求到来时,先通过布隆过滤器判断 key 是否可能存在:
    • 若不存在,直接返回空结果,避免访问 Redis 和 MySQL;
    • 若可能存在,再依次查询 Redis 和 MySQL。
1.4.3.2 缓存击穿

当某个热点数据在 Redis 中过期(或不存在),但在 MySQL 中存在时,大量并发请求会同时穿透到 MySQL 读取该数据,导致 MySQL 瞬间压力骤增

原因:热点数据的缓存失效,引发并发请求集中访问数据库。

解决方案

  1. 分布式锁

    • 当 Redis 中查询不到数据时,先尝试获取分布式锁,只有获取锁的请求才能访问 MySQL。
    • 该请求从 MySQL 读取数据后,更新 Redis 缓存,再释放锁;其他未获取到锁的请求则休眠一段时间后重试,直到从 Redis 中获取到数据。
  2. 热点 key 永不过期

    • 对于访问频率极高的热点数据,在 Redis 中设置为永不过期,避免因过期导致的缓存失效。
1.4.3.3 缓存雪崩

在某一时间段内,Redis 中大量缓存 key 集中过期失效,或 Redis 服务宕机,导致大量请求无法从缓存获取数据,全部涌向 MySQL,造成 MySQL 压力过载,甚至整个系统服务崩溃

原因

  • 缓存集中失效(如大量 key 设置了相同的过期时间);
  • Redis 集群宕机(如节点故障、网络问题);
  • 系统重启导致 Redis 缓存数据丢失(未开启持久化或重启时间过长)。

解决方案

  1. 避免缓存集中失效

    • 过期时间随机化:为 key 设置过期时间时,在基础时间上增加随机值,避免大量 key 同时过期。
    • 分批更新缓存:对热点数据的缓存过期时间进行错峰设计,通过定时任务分批刷新,避免集中失效。
  2. Redis 高可用集群

    • 采用 Redis 哨兵模式(Sentinel)或集群模式(Cluster),确保单个节点宕机时,其他节点能自动接管服务,避免 Redis 整体不可用。
  3. 缓存持久化与预热

    • 开启持久化:Redis 开启 RDB 或 AOF 持久化,确保重启时能恢复缓存数据(包括过期信息)。
    • 热数据预热:若系统重启时间较长,重启前通过脚本将 MySQL 中的热点数据提前加载到 Redis,避免重启后缓存为空导致请求冲击数据库。

更多资料:https://github.com/0voice


网站公告

今日签到

点亮在社区的每一天
去签到