MySQL缓存策略
一 缓存策略介绍
数据库提升读写性能的方式有:
- 连接池 阻塞IO和线程池
- 异步连接 非阻塞IO
- sql执行出发:即时执行+预编译执行
- 读写分离 对一致性要求不高
- 缓存方案
这篇blog重点就是介绍下MySQL的缓存方案。
MySQL缓存策略是指:
一般的项目中会有一个主要数据库,这个数据库一般就是关系型数据库,比如MySQL。为了提高MySQL的读写性能,会增加一个缓存数据库,用来缓存用户的热点数据。(MySQL中的缓存是为了缓存MySQL中的热点数据)。
对于用户热点数据,有五种情况:
- mysql有,cache没有 正常,通过策略避免
- mysql没有,cache有 不正常,避免
- mysql和cache都有,数据不一致 不正常,避免
- mysql和cache都有,数据一致 最终目标
- mysql和cache都没有 正常的
现在制定一下有缓存情况下的读写策略。
-
读策略:
- 先看cache是否有数据,如果有,直接返回;
- 如果没有,去访问mysql;
- 如果mysql有,缓存数据到cache;
- 如果mysql没有,就是没有。
-
写策略(这里做到了强一致性):
- 删除:先去删除缓存,再去删除mysql。为了避免发生2的情况。
- 修改:删除缓存,再去修改mysql
- 插入:删除缓存,再去修改mysql
当删除缓存并修改mysql后,mysql会主动将改变同步到缓存中。
这里假设用来缓存的数据库是redis。MySQL同步数据到redis,原理是主从复制(blog第二部分讲解)。
go-mysql-transfer会模拟成mysql的从节点,重mysql中复制某些指定的热点数据,并将它再次复制给redis。
上面这些缓存策略,逻辑上没有问题,但是实际上可能会有问题。
现在考虑下问题是否解决?是否有异常的情况?
显然,问题没有全部解决,我们刚刚思考的方向全是正常流程下的方式,我们来看异常情况。有三种异常的情况:1,缓存穿透;2,缓存击穿;3,缓存雪崩。
1. 缓存穿透
假设某个数据redis不存在,mysql也不存在,而且一直尝试读怎么办?缓存穿透,数据最终压力依然堆积在mysql,可能造成mysql不堪重负而崩溃;
解决方案:
- 发现mysql不存在,将redis设置为 <key, nil> 设置过期时间 下次访问key的时候 不再访问
mysql 容易造成redis缓存很多无效数据; - 布隆过滤器,将mysql当中已经存在的key,写入布隆过滤器,不存在的直接pass掉;
2. 缓存击穿
某些数据redis没有,但是mysql有;此时当大量这类数据的并发请求,同样造成mysql访问量过大;
解决方案:
- 加锁
请求数据的时候获取锁,如果获取成功,则操作,获取失败,则休眠一段时间(200ms)再去获取;获取成功,则释放锁首先读redis,不存在,读mysql,存在,写redis key的锁
整个流程走完,才让后面的服务器访问 - 将很热的key,设置不过期
3. 缓存雪崩
表示一段时间内,缓存集中失效(redis无 mysql 有),导致请求全部走mysql,有可能搞垮数据库,使整个服务失效;
解决方案:
- 如果因为缓存数据库宕机,造成所有数据涌向mysql;
采用高可用的集群方案,如哨兵模式、cluster模式; - 如果因为设置了相同的过期时间,造成缓存集中失效;
设置随机过期值或者其他机制错开失效时间; - 如果因为系统重启的时候,造成缓存数据消失;
重启时间短,redis开启持久化(过期信息也会持久化)就行了; 重启时间长提前将热数据导入redis当中;
二 主从复制
MySQL同步数据到redis,原理就是主从复制。主从复制组要解决了单点故障的问题,也解决了读写分离的问题。读写分离指的是向主节点写,从从节点读。读写分离只是保证最终一致性。
原理图:
- 主库更新事件(update、insert、delete)通过 io-thread 写到binlog(binlog是在引擎之上的);
- 从库请求读取 binlog,通过 io-thread 写入(write)从库本地 relay log(中继日志);
- 从库通过sql-thread读取(read) relay log,并把更新事件在从库中执行(replay)一遍;
复制流程:
- Slave上面的IO进程连接上Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容。
- Master接收到来自Slave的IO进程的请求后,负责复制的IO进程会根据请求信息读取日志指定位置之后的日志信息,返回给Slave的 IO进程。返回信息中除了日志所包含的信息之外,还包括本次返回的信息已经到Master端的bin-log文件的名称以及bin-log的位置。
- Slave的IO进程接收到信息后,将接收到的日志内容依次添加到Slave端的relay-log文件的最末端,并将读取到的Master端的 bin-log的文件名和位置记录到master-info文件中,以便在下一次读取的时候能够清楚的告诉Master从何处开始读取日志。
- Slave的Sql进程检测到relay-log中新增加了内容后,会马上解析relay-log的内容成为在Master端真实执行时候的那些可执行的内容,并在自身执行。