缓存介绍
Redis 最主要三个用途:
1)存储数据(内存数据库)
2)消息队列
3)缓存
对于硬件的访问速度,通常有以下情况:
CPU 寄存器 > 内存 > 硬盘 > 网络
缓存的核心思想就是,把一些常用的数据放到访问速度更快的地方,方便随时读取
对于计算机硬件来说,往往访问速度更快的设备,成本越高,存储空间越小
缓存也是如此,访问速度快,但是存储空间小,只能存储一些热点数据
二八定律:20%的热点数据,能够应对80%的访问场景
只需要把少量的热点数据缓存起来,就可以应对大多数的场景
使用 Redis 作为 MySQL 的缓存
在大部分项目中,都会使用关系型数据库(MySQL ..)来存储数据
关系型数据库虽然功能强大,但是性能不高(进行一次查询操作消耗的系统资源较多)
1)数据库把数据存储在硬盘上,硬盘的 IO 速度并不快,尤其是随机访问
2)如果查询不能命中,就需要进行表的遍历,会大幅增加硬盘 IO 次数
3)关系型数据库对于 SQL 的执行会做一系列的解析,校验,优化工作
4)一些比较复杂的查询,例如联合查询,需要进行笛卡尔积操作,效率更是较低很多
如果访问数据库的并发量比较高,对于数据库的压力会很大,容易使数据库服务器宕机
服务器每次处理请求,都需要消耗一定的硬件资源,包括但不限于 CPU,内存,硬盘,网络带宽 ..
一个服务器的硬件资源本身是有限的,一个请求消耗一份资源,随着请求的不断产生,自然把资源耗尽了。后续的请求没有资源可用,无法进行正常的处理,严重的会造成服务器崩溃
提高 MySQL 能承担的并发量 ——>
1)开源:引入更多机器,构成数据库集群
2)节流:引入缓存,把一些频繁读取的热点数据保存到缓存上
Redis 数据存储在内存中,访问内存比访问硬盘更快
Redis 只是支持 简单的 key-value 存储,不涉及复杂的查询规则
缓存的更新策略
1)定期生成
每隔一段时间,对于访问的数据频次进行统计,挑选出访问频次靠前的数据
以搜索引擎为例:
把用户访问的数据,通过日志的形式记录下来,按照不同的统计维度来定期更新
可以写一套离线的流程(往往使用 shell,python 写脚本代码),可以通过 定时任务 来触发
a) 完成统计热词的过程
b) 根据热词,找到搜索结果的数据
c) 把得到的缓存数据同步到缓存服务器上
d) 控制这些缓存服务器自动启动
优点:实现比较简单,过程可控,方便排查问题
缺点:实时性不够,不能处理突发情况
以上数据的统计可能会非常大,需要使用分布式的系统来春初这些日志(HDFS)
再使用 hadoop 的 map-reduce 来写代码进行统计,或使用基于 HDFS 的 HBASE 这样的数据库来写 sql 统计
2)实时生成
缓存会设有容量上限(通过 Redis 的配置文件的 maxmemory 参数设定)
接下来每用户查询:
如果在 Redis 查到了,就直接返回
如果在 Redis 中不存在,就从数据库中查,把查询到的结果同时写入 Redis
一段时间后会达到存储的上限,触发 “内存淘汰策略”
通常的淘汰策略:
FIFO (First In First Out) 先进先出
把缓存中存在时间最久的淘汰掉
LRU (Least Recently Used) 淘汰最久未使用的
记录每个 key 的最近访问时间,把最近访问时间最远的 key 淘汰掉
LFU (Least Frequently) 淘汰访问次数最少的
记录每个 key 最近一段时间的访问次数,把访问次数最少的淘汰掉
Random 随机淘汰
从所有 key 中随机淘汰掉一个
Redis 也提供了内置的淘汰策略,可以直接使用
• volatile-lru 当内存不足以容纳新写入数据时,从设置了过期时间的 key 中使用 LRU(最近最少使用)算法进行淘汰• allkeys-lru 当内存不足以容纳新写入数据时,从所有key中使用 LRU 算法进行淘汰• volatile-lfu 4.0版本新增,当内存不足以容纳新写入数据时,在过期的key中,使用 LFU 算法进行删除key• allkeys-lfu 4.0版本新增,当内存不足以容纳新写入数据时,从所有key中使用 LFU 算法进淘汰• volatile-random 当内存不足以容纳新写入数据时,从设置了过期时间的key中,随机淘汰数据• allkeys-random 当内存不足以容纳新写入数据时,从所有key中随机淘汰数据• volatile-ttl 在设置了过期时间的 key 中,根据过期时间进行淘汰,越早过期的优先被淘汰(相当于 FIFO, 只不过是局限于过期的 key)• noeviction 默认策略,当内存不足以容纳新写入数据时,新写入操作会报错
缓存使用注意事项
缓存预热 (Cashe preheating)
Redis 服务器首次接入数据库,服务器里没有数据,此时所有的请求都会打给 MySQL,随着时间的推移,Redis 上的数据越来越多,MySQL 承担的压力就逐渐减小 (实时生成)
客户端先查询 Redis,如果没查到就再查一次 MySQL,查到后会把数据写入 Redis
把 定期生成 和 实时生成 结合
先通过离线的方式,通过一些统计途径,先找到一些热点数据导入到 Redis 中,此时导入的这些热点数据,就能帮 MySQL 承担很大一部分压力,随着时间的推移,Redis 中也会用新的热点数据淘汰一些旧的数据
缓存穿透 (Cashe penetration)
访问的 key 在 Redis 和 MySQL 中都不存在,这样的 key 不会被放到缓存中,后续如果仍然在访问这个 key,依然会访问到 MySQL,会增加 MySQL 承担的请求压力
产生原因:
1)业务设计不合理,比如缺少必要的参数校验环节,导致非法的 key 也被查询
2)不小心把部分数据从数据库上误删
3)黑客恶意攻击
解决方案:
1)发现某个 key 在 Redis 和 MySQL 上都不存在,仍写入到 Redis 中,把 value 设置成 “”
2)引入布隆过滤器,每次查询 Redis / MySQL 之前,判定 key 是否在布隆过滤器上存在
布隆过滤器,本质上是结合了 hash + bitmap
以较小的空间开销,比较快的速度,实现针对 key 是否存在的判定
缓存雪崩 (Cashe avalanche)
在短时间内,Redis 上大规模的 key 失效,导致缓存命中率陡然下降,导致 MySQL 压力迅速上升,甚至宕机
产生原因:
1)Redis 直接挂了
Redis 宕机 / Redis 集群模式下大量节点宕机
2)Redis 运行良好,但是短时间内大量节点同时过期
给 Redis 设置 key 作为缓存时,为了考虑缓存的时效性,会设置过期时间,和 Redis 内存淘汰机制配合使用
解决方案:
1)部署高可用的 Redis 集群,完善监控报警机制
2)不给 key 设置过期时间 / 设置过期时间时添加随机的因子(避免同一时间过期)
缓存击穿 (Cache breakdown)
热点 key 突然过期,导致大量的请求直接打到 MySQL 上
1)基于统计的方式发现热点 key,并设置永不过期
往往需要服务器结构做出较大的调整
2)进行必要的服务降级,例如访问数据库时使用分布式锁,限制同时访问数据库的并发数
通过分布式锁,限制数据库的访问频率
在特定情况下,适当关闭一些不重要的功能,只保留核心功能(服务降级)——> 省点模式