众所周知,常用的关系型数据库MySQL底层是以B+树来组织存储在磁盘中的数据,而由于磁盘IO的读写性能较差,加之实际业务场景中读操作的次数要数倍于写操作。因此,适当的将读写操作分离,设计一种合适的缓存策略对提升MySQL性能异常重要。
本文讲述的重点放在MySQL读写分离和缓存方案上,同时介绍MySQL的主从复制原理,对缓存方案中存在的数据同步问题进行分析并给出一致性方案,最后对三类常见的缓存问题进行分析。
MySQL的主从复制主要解决的问题是数据备份、高可用性、故障切换等,在本文中提出主要是为了进一步区分缓存策略和主从复制解决的痛点问题。
先上一张《高性能MySQL》中的图:
图中主要描述了MySQL复制的三个流程:
第一步: 在主库上记录二进制日志(Binary Log),即binlog。每次准备提交事务完成数据更新前,主库会将要更新的事件记录到binlog当中,值得注意的是,binlog中是按照事务提交的顺序来记录的,而非直观上的语句执行顺序,在记录完日之后,主库就会告诉存储引擎可以提交事务啦!
第二步: 从库将主库的binlog复制到本地的中继日志(Relay Log)。从库首先会启动一个IO线程来跟主库建立连接,接着在主库上启动一个名为“二进制转储”线程,用于读取主库上binlog中的事件,然后从库上的IO线程将接收到的事件记录到relay-log当中。
第三步: 从库上的SQL线程读取relay-log中的事件并重放执行,实现数据更新。值得注意的是,从库的IO线程和SQL线程是独立工作的,类似于生产者消费者模型,实现了获取事件和重放事件的解耦,同时也限制了复制的过程,成为了一些工作的性能瓶颈。
读写分离的结构如下图所示:
一般将缓存数据库作为辅助数据库,存放热点数据,MySQL则是主要数据库。
学过《计组》的同学们都知道,内存的访问速度是磁盘访问速度的10万倍(数量级倍率),内存的访问速度大约是100ns,而一次磁盘访问大约是10ms;此外,访问mysql时访问磁盘的次数跟b+树的高度相关,因此,引入缓存意义就非常重要。
值得注意的是,MySQL内部也有缓存层来保存热点数据,其与具体业务无关,只是包括数据文件、索引文件等,这里以秒杀活动为例,区分MySQL内部缓冲池和缓存数据库。
假设12点有一个秒杀活动,11点有大量用户注册,这就会在MySQL内部缓冲池保存部分数据,但这些用户并不是活跃的,只有在12点左右才是活跃的,也就是所说的用户热点数据,一般选择将这些数据放到缓存数据库(如redis,memcached)当中,提高MySQL性能。
如图3.1中的架构图所示,引入缓存后就存在数据的同步问题,对于指定数据操作时,一般存在下面几种情况:
显然,我们只需要考虑前三种异常情况,为此,我们对数据库操作进行调整:
方案一
方案二
不同于方案一操作的繁琐和效率低下,方案二采用了过期时间的概念,具体操作如下:
以上考虑的都是逻辑自洽的方案,那么当缓存发生异常时应该怎么处理呢,下面我们列举几种常见的缓存异常的情况。
情形
某类数据既不在redis也不在MySQL,而客户还是一直在尝试读,这时就会产生缓存穿透,数据读取的压力都集中在MySQL,可能造成MySQL不堪重负至奔溃。
解决
情形
某类数据不在redis中,而MySQL中有,此时发生对该类数据的大量并发请求时,就会造成MySQL压力过大的情况。
解决
情形
当redis失效时,出现redis无数据,MySQL有数据,导致请求全部走mysql,有可能搞垮数据库,使整个服务失效。
解决
缓存数据库在整个系统不是必须的,也就是缓存宕机不会影响整个系统提供服务。
本文系统的总结了MySQL缓存方案的必要性,对引入缓存后的数据同步问题进行分析,最后再对三种缓存异常情形进行总结,列举常用的解决方案,希望能帮助同学们理清思路哈!
参考
《高性能MySQL-第三版》
https://blog.csdn.net/yuan2019035055/article/details/122310447
https://blog.csdn.net/yangbindxj/article/details/123307888?spm=1001.2100.3001.7377&utm_medium=distribute.pc_feed_blog_category.none-task-blog-classify_tag-7.nonecase&depth_1-utm_source=distribute.pc_feed_blog_category.none-task-blog-classify_tag-7.nonecase