目录
缓存基本概念
二八定律
Redis 作为缓存
缓存更新策略
定期生成
实时生成
内存淘汰策略
缓存使用的注意事项
关于缓存预热
关于缓存穿透
关于缓存雪崩
关于缓存击穿(瘫痪)
缓存基本概念
- 所谓缓存,其实就是将一部分常用数据放到访问速度更快的地方,方便随时读取
实例理解
- 硬件的访问速度一般为:CPU 寄存器 > 内存 > 硬盘 > 网络
- 此时最常见的 便是使用 内存 作为 硬盘 的缓存,即 Redis 定位
- 当然,硬盘 也能作为 网络 的缓存!因为硬盘的访问速度相对网络来说更快!
- 比如 浏览器缓存
- 浏览器通过 http/https 从服务器上获取数据(html、css、js、图片、视频、音频、字体 等)并进行展示
- 像 图片、视频、音频等 这些体积大 又不太会改变的数据就可以保存到浏览器本地,即浏览器所在主机的硬盘上
- 等到后续再打开该网页时,就不必重新从网络获取上述数据了!
二八定律
- 指 20% 的数据,可以应对 80% 的请求
注意:
- 缓存虽然速度快,但空间小
- 大部分情况下,缓存仅存放一些热点数据即可,因为这些热点数据足以应对大多数场景
Redis 作为缓存
- 我们通常使用 Redis 作为数据库(MySQL)的缓存
注意:
- 数据库是十分重要的组件,即绝大部分商业项目均会涉及
- 但 MySQL 的访问速度又相对较慢,因此我们便可使用 Redis 来作为 MySQL 的缓存
问题:
- 为什么说关系型数据库性能不高?
回答:
- 数据库把数据存储在硬盘上,但硬盘的 IO 速度并不快,尤其是随机访问
- 如果查询不能命中索引,便需进行表的遍历,这就会大大增加硬盘的 IO 次数
- 关系型数据库对应 SQL 的执行会做一系列的解析、校验、优化工作
- 如果是一些复杂查询,如联合查询,需要进行笛卡尔积操作,效率更是降低很多
注意:
- 因为 MySQL 等数据库,效率相对较低,所以承担的并发量有限,一旦请求量多了,数据库的压力就会很大,甚至于很容易便宕机了
- 服务器每处理一个请求,都需消耗一些硬件资源(CPU、内存、硬盘、网络 等)
- 任意一种资源的消耗超出了机器所能提供的上限,此时机器便会很容易出现故障
问题:
- 如何提高 MySQL 所能承担的并发量?(客观需求)
回答:
- 开源:引入更多的机器,构成数据库集群
- 节流:引入缓存,将一些频繁读取的热点数据,保存到缓存上,后续在查询数据时,如果缓存中已存在,便不再访问 MySQL,直接从缓存中拿数据即可(典型的方案)
缓存更新策略
- 此处我们解决关于 如何知道 Redis 中应存储哪些数据,即哪些数据为 热点数据?
定期生成
- 将访问数据,以日志形式给记录下来
- 通过日志,将都使用到了哪些词给记录下来,此时便可以针对这些日志信息进行统计了
- 统计这 一天/一周/一个月 每个词出现的频率,再根据频率降序排序
- 再取出前 20% 的词,就可以认定这些词为 热点词
- 接下来就可以将这些热点词 所涉及到的搜索结果给提前拎出来,并放到类似于 Redis 这样的缓存中了
注意点一:
- 当然上述所讲的数据量是非常大的,所以我们可以写个程序来进行统计
- 数据量可能大到说 一台机器都存不下
- 此时便需要使用分布式的系统来存储这些日志(HDFS)
- 再使用 hadoop 的 map-reduce 来写代码进行统计
- 也可基于 HDFS 的 HBASE 这样的数据库来写 sql 统计
- 这属于 大数据工程师 的日常工作
注意点二:
- 此处的数据可以根据当前这里的统计纬度,来定期更新
- 按照天级别统计的,就每天更新一次
- 按照月级别统计的,就每个月更新一次
- 写一套离线的流程(往往使用 shell,python 写脚本代码)
- 然后通过 定时任务 的形式来触发
定时任务的内容包括:
- 完成统计热词的过程
- 根据热词,找到搜索结果的数据
- 将得到的缓存数据同步到缓存服务器上
- 控制这些缓存服务区自动重启
优点:
- 上述过程,实际上实现起来还是比较简单的
- 其过程更可控,缓存中的数据大多都比较固定,方便排查问题
缺点:
- 实时性不够,如果出现一些突发性事件,有一些本来不是热词的内容成了热词
- 此时新的热词就可能给后面的数据库啥的代码较大的压力
- 比如 春节晚会 这个突发性热词
实时生成
- 如果在 Redis 中查到了,就直接返回
- 如果在 Redis 中查不到,就从数据库查,并将查到的结果同时写入 Redis
- 这样不停的写 Redis,就会使 Redis 的内存占用越来越多
- 逐渐达到内存上限,此处的内存上限可通过配置 maxmemory 参数设定,即不一定指的是机器内存上限,Redis 中可通过配置设定最多使用多少内存
- 达到内存上限后,再继续往 Redis 中插入数据的话,便会触发内存不够问题!
- 而为了解决改问题,Redis 便引入了 内存淘汰策略
通过内存淘汰策略,并经过一段时间的动态平衡,Redis 中的 key 将逐渐变为热点数据
内存淘汰策略
- FIFO【先进先出】:将内存中存在时间最久的(也就是先来的数据)给淘汰掉
- LRU【淘汰最久未使用的】:记录每个 key 的最近访问时间,将最近访问时间最老的 key 给淘汰掉
- LFU【淘汰访问次数最少的】:记录每个 key 最近一段时间的访问次数,将访问次数最少的 key 给淘汰掉(相对靠谱)
- Random【随机淘汰】:从所有的 key 中抽取幸运儿被随机淘汰掉(不太合理)
注意:
- 当然具体采取哪种策略,还需结合实际场景来具体问题具体分析
- Redis 中有一个配置项,可以设置 Redis 采取上述哪种策略淘汰内存数据
缓存使用的注意事项
关于缓存预热
- 当 Redis 服务器首次接入时,服务器中是没有数据的
- 客户端先查询 Redis,如果没有查到,则再查 MySQL
- 查到了之后,便会将数据给写入到 Redis 中
- 针对上述情况,此时所有请求均会打给 MySQL
- 并随着时间的推移,Redis 上的数据也越积越多
- 自然 MySQL 所承担的压力也就越来越小了
解决方案:
- 缓存预热 就是用来解决上述问题的
- 此处我们可以将 定期生成 和 实时生成 给结合起来
- 先通过离线的方式,根据统计,先找到一批热点数据,并将其导入到 Redis 中
- 此时导入的这批热点数据,就能帮 MySQL 承担很大一部分压力了
- 最后随着时间的推移,逐渐使用新的热点数据淘汰旧的热点数据
注意:
- 定期生成 是不涉及预热的,仅实时生成涉及该问题
关于缓存穿透
- 查询某个 key 时,Redis 和 MySQL 中均没有
- 便可能导致 这次查询 ——> 没有,下次查询 ——> 仍然没有 的情况
- 如果存在很多像这样的 key,并且还反复查询,一样也会给 MySQL 带来很大的压力
产生原因:
- 业务设计不合理,如缺少必要的参数校验环节,导致非法 key 也被运行查询(典型)
- 开发 或 运维误操作,不小心将部分数据从数据库上误删了(没那么典型,表现也是缓存穿透,误删操作,不一定能即时发现)
- 黑客恶意攻击(比较少见)
不靠谱的解决方案:
- 通过改进业务 或 加强监控报警(亡羊补牢)
- 事故出现之后,才采取行动!
靠谱的解决方案:(减低问题的严重性)
- 如果发现该 key 在 Redis 和 MySQL 上均不存在,此时直接将 key 写入 Redis 中,并将其 value 设成一个非法值(如 " ")
- 引入 布隆过滤器,将所有的 key 均插入到 布隆过滤器 中,每次查询 Redis 或 MySQL 前都先判定一下该 key 是否在存在于 布隆过滤器 上
具体解释:
- 布隆过滤器,本质上结合了 哈希 与 位图 ,以比较小的空间开销,比较快的时间速度,实现针对 key 是否存在的判定
关于缓存雪崩
- 短时间内,Redis 上大规模的 key 失效,导致缓存命中率陡然下降,进而导致 MySQL 的压力迅速上升,甚至直接宕机
产生原因:
- Redis 直接挂了(Redis 宕机 或 Redis 集群模式下大量节点宕机)
- Redis 虽正常运行,但可能由于之前短时间内设置了很多 key 给 Redis,且设置的过期时间均在同一时刻过期
注意:
- 给 Redis 里设置 key 作为缓存时,有时候为了考虑缓存的时效性,便会设置过期时间(Redis 内存淘汰机制 配合着使用)
解决方案:
- 加强监控报警,加强 Redis 集群可用性的保证(集群监控、哨兵监控)
- 不给 key 设置过期时间 或 设置过期时间时通过添加随机因子来避免同一时刻过期
关于缓存击穿(瘫痪)
- 相当于缓存雪崩的特殊情况
- 此处是针对热点 key 突然过期,进而导致大量的请求直接访问到 MySQL 上,甚至直接引起数据库宕机
注意:
- 热点 key 访问频率更高,影响更大
解决方案:
- 基于统计的方式筛选出热点 key ,并设置永不过期
- 进行必要的服务降级
理解服务降级:
- 例如访问 MySQL 时,使用分布式锁,限制同时请求数据库的并发量
- 例如本身服务器的功能有十个,但在特定情况下,可适当关闭一些不重要的功能,只保留核心功能(省电模式)