写点什么

MySQL 缓存策略分析

作者:C++后台开发
  • 2022 年 5 月 20 日
  • 本文字数:2390 字

    阅读完需:约 8 分钟

一、背景介绍

众所周知,常用的关系型数据库 MySQL 底层是以 B+树来组织存储在磁盘中的数据,而由于磁盘 IO 的读写性能较差,加之实际业务场景中读操作的次数要数倍于写操作。因此,适当的将读写操作分离,设计一种合适的缓存策略对提升 MySQL 性能异常重要。

本文讲述的重点放在 MySQL 读写分离和缓存方案上,同时介绍 MySQL 的主从复制原理,对缓存方案中存在的数据同步问题进行分析并给出一致性方案,最后对三类常见的缓存问题进行分析。

二、MySQL 主从复制原理

MySQL 的主从复制主要解决的问题是数据备份、高可用性、故障切换等,在本文中提出主要是为了进一步区分缓存策略和主从复制解决的痛点问题。

先上一张《高性能 MySQL》中的图:

图中主要描述了 MySQL 复制的三个流程:

第一步: 在主库上记录二进制日志(Binary Log),即 binlog。每次准备提交事务完成数据更新前,主库会将要更新的事件记录到 binlog 当中,值得注意的是,binlog 中是按照事务提交的顺序来记录的,而非直观上的语句执行顺序,在记录完日之后,主库就会告诉存储引擎可以提交事务啦!

第二步: 从库将主库的 binlog 复制到本地的中继日志(Relay Log)。从库首先会启动一个 IO 线程来跟主库建立连接,接着在主库上启动一个名为“二进制转储”线程,用于读取主库上 binlog 中的事件,然后从库上的 IO 线程将接收到的事件记录到 relay-log 当中。

第三步: 从库上的 SQL 线程读取 relay-log 中的事件并重放执行,实现数据更新。值得注意的是,从库的 IO 线程和 SQL 线程是独立工作的,类似于生产者消费者模型,实现了获取事件和重放事件的解耦,同时也限制了复制的过程,成为了一些工作的性能瓶颈。

三、读写分离

3.1 基本架构

读写分离的结构如下图所示:

​一般将缓存数据库作为辅助数据库,存放热点数据,MySQL 则是主要数据库。

3.2 缓存必要性

学过《计组》的同学们都知道,内存的访问速度是磁盘访问速度的 10 万倍(数量级倍率),内存的访问速度大约是 100ns,而一次磁盘访问大约是 10ms;此外,访问 mysql 时访问磁盘的次数跟 b+树的高度相关,因此,引入缓存意义就非常重要。

值得注意的是,MySQL 内部也有缓存层来保存热点数据,其与具体业务无关,只是包括数据文件、索引文件等,这里以秒杀活动为例,区分 MySQL 内部缓冲池和缓存数据库。

假设 12 点有一个秒杀活动,11 点有大量用户注册,这就会在 MySQL 内部缓冲池保存部分数据,但这些用户并不是活跃的,只有在 12 点左右才是活跃的,也就是所说的用户热点数据,一般选择将这些数据放到缓存数据库(如 redis,memcached)当中,提高 MySQL 性能。

3.3 同步问题分析

如图 3.1 中的架构图所示,引入缓存后就存在数据的同步问题,对于指定数据操作时,一般存在下面几种情况:

  1. mysql 有,缓存无

  2. mysql 无,缓存有

  3. 都有,但数据不一致

  4. 都有,数据一致

  5. 都没有

显然,我们只需要考虑前三种异常情况,为此,我们对数据库操作进行调整:

方案一

  1. 在进行删除操作时,先删除 redis 数据,再删除 MySQL 数据。

  2. 在进行修改操作时,先删除 redis 数据,再修改 MySQL 数据。

  3. 在进行插入操作时,先删除 redis 数据,在修改 MySQL 数据。

方案二

不同于方案一操作的繁琐和效率低下,方案二采用了过期时间的概念,具体操作如下:

  1. 在进行删除操作时,先删除 redis 数据,再删除 MySQL 数据。

  2. 在修改和插入操作时,首先修改 redis 数据,并设置过期时间(如 100ms),等修改完 MySQL 并同步 redis 时再删除过期时间。

四、缓存异常

以上考虑的都是逻辑自洽的方案,那么当缓存发生异常时应该怎么处理呢,下面我们列举几种常见的缓存异常的情况。

4.1 缓存穿透

情形

某类数据既不在 redis 也不在 MySQL,而客户还是一直在尝试读,这时就会产生缓存穿透,数据读取的压力都集中在 MySQL,可能造成 MySQL 不堪重负至奔溃。

解决

  1. 发现 mysql 不存在,将 redis 设置为 <key, nil> 并设置过期时间,下次访问 key 的时候就不再访问 mysql,当然这容易造成 redis 缓存很多无效数据。

  2. 采用布隆过滤器,将 mysql 当中已经存在的 key,写入布隆过滤器,不存在的直接 pass 掉。(关于布隆过滤器可以看这篇:海量数据去重、HASH、布隆过滤器介绍)

4.2 缓存击穿

情形

某类数据不在 redis 中,而 MySQL 中有,此时发生对该类数据的大量并发请求时,就会造成 MySQL 压力过大的情况。

解决

  1. 加锁。请求数据的时候获取锁,如果获取成功则操作,获取失败则休眠一段时间(200ms)再去获取;获取成功后,释放锁。此时一般的流程是:首先读 redis,不存在,读 mysql,存在,写 redis key 的锁,整个流程走完,才让后面的服务器访问。

  2. 将很热的 key,设置不过期,即频繁使用的用户数据常驻在 redis 中。

4.3 缓存雪崩

情形

当 redis 失效时,出现 redis 无数据,MySQL 有数据,导致请求全部走 mysql,有可能搞垮数据库,使整个服务失效。

解决

缓存数据库在整个系统不是必须的,也就是缓存宕机不会影响整个系统提供服务。

  1. 如果因为缓存数据库宕机,造成所有数据涌向 mysql。可以采用高可用的集群方案,如哨兵模式、cluster 模式;

  2. 如果因为设置了相同的过期时间,造成缓存集中失效。可以设置随机过期值或者其他机制错开失效时间,也就是错峰策略;

  3. 如果因为系统重启的时候,造成缓存数据消失。若重启时间短,将 redis 开启持久化(过期信息也会持久化); 重启时间长则提前将热数据导入 redis 当中。

五、总结

本文系统的总结了 MySQL 缓存方案的必要性,对引入缓存后的数据同步问题进行分析,最后再对三种缓存异常情形进行总结,列举常用的解决方案,希望能帮助同学们理清思路哈!

参考资料


​推荐一个零声教育 C/C++后台开发的免费公开课程,个人觉得老师讲得不错,分享给大家:C/C++后台开发高级架构师,内容包括Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

以下学习资料,C++后台开发面试题,教学视频,C++后台开发学习路线图,免费分享有需要的可以自行添加:学习资料群720209036 自取


原文链接:https://blog.csdn.net/hjlogzw/article/details/123729607

用户头像

还未添加个人签名 2022.05.06 加入

还未添加个人简介

评论

发布
暂无评论
MySQL缓存策略分析_MySQL_C++后台开发_InfoQ写作社区