MySQL缓存策略
在开始本期讲解之前,我们需要明确最重要的一个问题
MySQL缓存方案缓存的是什么?
缓存的时用户定义的热点数据,用户直接从缓存获取热点数据,降低读写压力。
这里要注意,缓存的是用户定义的热点数据,所以大家要明白MySQL本身的缓存区和这里缓存策略的缓存区是两码事。
我们用什么来缓存?
这里采用的是redis来缓存MySQL的数据,大家都知道redis是内存数据库,读取数据的速度比在磁盘读取快得多。大概是十万倍。
当然我们也不是盲目的什么时候都用缓存方案,在读的需求远远大于写的需求时我们用缓存方案,因为如果写是主要的,我们这个缓存区就没啥大用了。
同时MySQL作为项目主要的数据库,便于统计分析,redis作为辅助数据库,存放热点数据。
提升MySQL性能的方法
读写分离
主从复制
在实现读写分离之前需要先实现主从复制
1. 主库更新事件 DML 操作( update 、 insert 、 delete ) 通过 io-thread 写到 binlog;
2. 从库请求读取 binlog,通过 io-thread 写入从库本地 relay-log(中继日志);
3. 从库通过 sql-thread 读取 relay-log,并把更新事件在从库中重放(replay)一遍;
读写分离
读操作从从数据库中读,写操作只写主数据库。但是在同步过程中难免导致数据不一致,能保证最终一致性。
缓存方案的解决
没有缓冲层之前,我们对数据的读写都是基于 mysql;所以不存在同步问题;这句话也不是必然, 比如读写分离就存在同步问题(数据一致性问题); 引入缓冲层后,我们对数据的获取需要分别操作缓存数据库和 mysql;那么这个时候数据可能下面几个状态:
1. mysql 有,缓存无
2. mysql 无,缓存有
3. 都有,但数据不一致
4. 都有,数据一致
5. 都没有
显然我们可以接受4和5,因为都是正常情况。那么在其他情况下我们还能接收那个呢。
首先明确一点:我们获取数据的主要依据是 mysql,只需要将 mysql 的数据正确同步到缓存数据库 就可以了;同理,缓存有,mysql 没有,这比较危险,此时我们可以认为该数据为脏数据;所以我 们需要在同步策略中避免该情况发生;同时可能存在 mysql 和缓存都有数据,但是数据不一致,这种也需要在同步策略中避免。
具体热点数据读写策略
读策略很简单,先从redis里读,如果没有就从MySQL里读。
主要的分歧在写里
以安全为主
写流程:先删除缓存,再写 mysql,后面数据同步交由 go-mysql-transfer 等中间件处理;(将问题 3 转化成 1) 先删除缓存,为了避免其他服务读取旧的数据;也是告知系统这个数据已经不是最新,建议从 mysql 获取数据; 但是对于服务 A 而言,写入 mysql 后,接着读操作必须要能读到最新的数据。
但是大家要明白,我们设置缓存方案的主要目的就是提升效率,现在却为了数据安全降低了效率,所以这种方案是很少使用的,除非是读远大于写的情况下才使用。
以效率为主
写流程:先写缓存,并设置过期时间(如 200ms),再写 mysql,后面数据同步交由其他中间件 处理; 这里设置的过期时间是预估时间,大致上是 mysql 到缓存同步的时间; 在写的过程中如果 mysql 停止服务,或数据没写入 mysql,则 200 ms 内提供了脏数据服务;但仅 仅只有 200ms 的数据错乱;
这里设置的过期时间其实就是与MySQL网络传输时间+MySQL处理事件+MySQL同步到redis的时间
安全问题就是在这200ms中可能提供脏数据,但几率很小了。
同步方案
go-mysql-transfer
这种方案其实是通过伪装从数据库来将MySQL同步到redis的方案
1.首先修改MySQL配置文件 my.ini,使主数据库和从数据库的id不同
2.修改app.yml配置端口信息以及配置redis地址
3.配置热点数据的具体信息
4.写lua同步逻辑
触发器+UDF
这种方案其实是在MySQL中使用c++的函数实现同步的,但是这种方案不具备事务,不能回滚,使用较少。
缓存方案的故障及解决
缓存穿透
问题:数据在缓存和mysql都无,一直读取不存在的数据,造成mysql访问性能急剧下降
解决:
1.缓存设置<key,nil>这样给读取一个空数据
2.部署布隆过滤器,这个过滤器可以先检查redis和mysql里是否有这个数据,有就读没有直接返回
缓存击穿
问题:缓存无,mysql有,大量的并发连接请求造成mysql访问性能急剧下降
解决:
1.过热数据不过期:在mysql同步到redis的数据也会设置过期时间,面对缓存击穿可以设置过热数据不过期,这样就访问redis即可
2.分布式锁,请求数据的时候获取锁,若获取成功,则操作后释放锁;若获取失败,则休眠一段时间(200ms) 再去获取,当获取成功,操作后释放锁
缓存雪崩
问题:表示一段时间内,缓存集中失效(redis 无, mysql 有),导致请求全部走 mysql,有可能搞垮数 据库,使整个服务失效。
解决:
1.如果因为设置了相同的过期时间,造成缓存集中失效; 设置随机过期值或者其他机制错开失效时间;
2.如果因为缓存数据库宕机,造成所有数据涌向 mysql; 采用高可用的集群方案,如哨兵模式、cluster 模式;
3.如果因为系统重启的时候,造成缓存数据消失; 重启时间短,redis 开启持久化(过期信息也会持久化)就行了; 重启时间长提前将热数据导 入 redis 当中。
更多资料在:https://github.com/0voice查询