Mysql缓存方案

Posted qq_34132502

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mysql缓存方案相关的知识,希望对你有一定的参考价值。

读写分离

为什么需要读写分离

mysql主要是针对磁盘进行操作,虽然Mysql内部有一些缓冲区,但读性能是非常低的,速度慢。而在大多数项目中,读操作是要远远大于写操作的,所以如果将读写分离,可以更好地分别提高读写性能。
但是读写分离可能会造成一些问题,如读和写操作的数据不一致,且部署维护困难。

mysql主从复制

binlog

二进制日志,是在事务提交之后产生的,跟存储引擎没什么关系。而redolog在事务提交之后也有一个整体的刷盘操作。binlog记载的是修改的行信息。

在这里插入图片描述

  1. 主库更新事件(update、insert、delete)通过io-thread写到binlog;
  2. 从库请求读取binlog,通过io-thread写入(write)从库本地 relay log(中继日志);
  3. 从库通过sql-thread读取(read) relay log,并把更新事件在从库中执行(replay)一遍;

事务提交之后产生binlog,我们的从数据库会开启一个IO线程,从主数据库复制binlog到从数据库中。从数据库再把它写到一个中继日志。之后我们再开启另一个线程SQL thread,它负责将中继日志的内容读出来然后,进行回放,即,将在主数据库中执行过的sql语句再执行一遍。这是主从数据库将保持一致。

在这里插入图片描述
如果从数据库越多,同步也就越慢

最终一致性、强一致性

最终一致性

写主读从
在这里插入图片描述

强一致性

写主;如果一致性要求高从主数据库中读,如果一致性要求不高从从数据库中读
在这里插入图片描述

缓存方案

前提:读多写少,单个主节点能够承受项目数据量

热点读数据在缓存数据库备份,将热点读操作转移到缓存数据库

在这里插入图片描述

应用场景分析

  1. 内存访问速度是磁盘访问速度的十万倍
  2. 大所属业务场景下读次数是写次数的十倍以上

所以我们需要去优化它的读性能

mysql自带内部的缓存,但这个缓存主要是针对mysql自身的需求的。而mysql主要需要解决的是内存访问与磁盘访问的速度差异,跟我们的业务需求不同。

例如在整点秒杀活动中,我们可以提前缓存一些用户数据,以防用户大量登陆造成的mysql读性能下降

通常使用关系型数据库作为主数据库,便于分析(因为内存比磁盘小得多)
使用缓存数据库(内存数据库)存放热点数据

在这里插入图片描述

同步问题分析

因为引入了缓存数据库,所以需要引入同步策略,将缓存数据库和mysql的数据保持一致

缓存可以宕机,出现异常,但需要保证我们系统必须可以继续运行。

存在状态

mysql有,缓存无

这种情况下可以接受,但是需要通过策略,将mysql中的数据同步到缓存中,这样方便我们下一次直接从缓存中取数据

mysql无,缓存有

不可接受,因为mysql是作为我们主要数据的依据、基准。如果mysql中没有数据,那么其他的,比如先访问缓存数据库的话,缓存中有,会造成我们整个系统的数据不一致。所以我们需要通过某种策略去避免这种情况的发生

mysql有,缓存有,但是数据不一致

同样不可接受,原因同上。也同样需要通过策略去避免。

mysql和缓存都有数据,且一致

可接受

mysql和缓存都没有数据

可接受

目标场景

单个数据中心

部分游戏业务场景,有单独的DBServer

多个数据中心

大部分web项目,server端不缓存数据,直接操作数据库;如果缓存数据可能造更新丢失
在这里插入图片描述

最终一致性解决方案

读写分离,主库将数据同步到从库,是需要时间,那么在同步期间,主从之间数据有差异。

读方案:先读缓存,缓存存在直接返回;缓存不存在,去访问mysql获取,再写入Redis。

这里有写两种方案:

  1. 直接写mysql,等待mysq|同步数据到redis
  2. 先写redis,设置key的过期时间为200ms(经验值),等待mysq|回写redis,覆盖key,设置更长的过期时间(因为,如果这时有其他人需要访问这个数据,就可以直接从Redis中读取,不需要去mysql,加快速度);200ms默认的是写mysq|到mysq同步到redis的时长;这个需要根据实际环境进行设置。可能存在多次访问mysql(即超过了200ms没有返回,则返回时key不存在,所以会造成Redis再去访问mysql ),也可能存在更新丢失(即多个服务器操作同一流程,我们在Redis写完,等它同步过来的时候,又有另一个服务器进行修改,会造成内存中的数据不一致)
    在这里插入图片描述
    在这里插入图片描述

强一致性解决方案

读方案:和同步一致性一样,先读缓存,缓存存在直接返回;缓存不存在,去访问mysql获取,再写入Redis

写方案:先删除缓存,再写mysql,等待mysql同步到缓存(否则在还未同步成功时,会出现数据不一致)
在这里插入图片描述
在这里插入图片描述

数据同步方案

不管强一致性还是最终一致性,都需要一个同步的流程,把mysql的数据同步到Redis中。接下来解释同步中的几种方案。

方案一(canel)

依照主从复制的原理,这个canel伪装成从数据库,实现了如下的部分(红),即读取binlog
canel client则实现了将binlog这个二进制文件解析成可读的格式

在这里插入图片描述

在这里插入图片描述

方案二

不建议使用,有事务的场景容易出错;虽然保证了真正的强一致性;
这个实现每次插入修改都需要重新简历redis连接,操作完后又释放redis连接;

在这里插入图片描述

在这里插入图片描述

缓存故障

下面三个问题都是读缓存没有,然后再去读mysql的过程中出现了问题

缓存穿透

问题:当缓存和mysql都不存在:先读缓存,如果缓存不存在再读mysql,如果mysql不存在则直接返回。如果黑客利用mysql中不存在的数据,它就会一直请求mysql中不存在的数据,压力会全部堆积在mysql中,那么这时候可能会造成mysql的崩溃(包括假死,数据库无法提供服务的这种状态)。

解决:

  1. 缓存中设置<key, nil>与过期时间(防止堆积过多<key, nil>),下次访问key的时候,不再访问mysql。
  2. 部署布隆过滤器(将mysql中存在的key写入布隆过滤器)(布隆过滤器不支持删除的功能,只支持添加的功能,所以限制过多)

缓存击穿

问题:某些数据Redis没有,但mysql有,此时有大量这类数据的并发请求,同样会造成mysql的压力过大。(这种很热的key过期了)

解决:

  1. 加锁:从mysql请求数据的时候获取锁,如果获取成功,则进行接下来的操作;如果获取失败,则休眠一段时间(200ms)再去获取;获取成功,则同步到Redis中,最后再释放锁。释放锁之后,其他的进程可以并发的访问Redis,并发访问Redis并没有问题。
  2. 将很热的key设置不过期

在这里插入图片描述

缓存雪崩

问题:表示一段时间内,大量缓存集中失效(redis无 mysql有),导致请求全部走mysql,有可能搞垮数据库,使整个服务失效

解决:

  1. 如果因为缓存数据库宕机,造成所有数据涌向mysql; 采用高可可用的集群方案,如哨兵模式(通过选举会选举出另外一个节点,作为缓存主数据库)、cluster模式;
  2. 如果因为设置了相同的过期时间,造成缓存集中失效;设置随机过期值或者其他机制错开失效
  3. 如果因为系统重启的时候,造成缓存数据消失(Redis是内存数据库);重启时间短,redis开启持久化(过期信息也会持久化)就行了;重启时间长提前将热数据导入redis当中;

在这里插入图片描述

以上是关于Mysql缓存方案的主要内容,如果未能解决你的问题,请参考以下文章

MySQL缓存策略详解

Android主流视频播放及缓存实现原理调研

MySQL与Redis缓存的同步方案

Swift新async/await并发中利用Task防止指定代码片段执行的数据竞争(Data Race)问题

Swift新async/await并发中利用Task防止指定代码片段执行的数据竞争(Data Race)问题

是否可以使用 AVPlayer 缓存 HLS 段?