什么是缓存?
缓存是计算机中一个经典的概念,其实本质就是将常用的数据放到访问速度快的地方,方便读取。
对于硬件的访问速度来说,通常情况如下:
CPU寄存器 > 内存 > 硬盘 > 网路
但是对于计算机硬件来说,往往访问速度快的设备,成本就会越高,存储空间就会越小。缓存是快,但是空间是不足的,所以,缓存大部分就是放一些热点数据。
在一个网站中,通常使用像MySQL这样的关系型数据库来存储数据。关系型数据库虽然功能强大,但是有一个缺陷就是性能不高(这里是相对于redis来说)
为什么说关系型数据库性能不高?
- 数据库将数据存储到硬盘上,硬盘上的IO速度并不快,尤其是随机访问
- 如果查询不能命中索引,就需要进行表的遍历,这会大大增加硬盘IO次数
- 如果涉及到了一些复杂的查询,需要用到笛卡尔积,效率就会下降很多
而且,在面对并发量大的情况下,数据库可能会宕机
为什么并发量大的情况下会宕机?
- 服务器每次处理一个请求,都是需要消耗一定的硬件资源的(CPU、内存、硬盘、网络带宽)
- 一个服务器的资源是有限的,一个请求消耗一份资源,请求多了,后续的请求就没有资源可用了,无法得到处理,就有可能会宕机
如何让数据库承担更大的并发量呢?
- 开源:引入更多的机器,部署更多的数据库实例,构成集群。比如:分库分表、主从复制等
- 节流:引入缓存,使用其他的方式保存经常访问的数据,从而降低数据库的压力
其中Redis就是缓存的一个经典案列,Redis的访问速度比MySQL快,或者说处理同一个访问请求,Redis消耗的系统资源比MySQL少很多,因此Redis能支持的并发量大
- Redis数据在内存中,访问速度比硬盘快
- Redis只支持key-value的形式,不涉及复杂查询的限制规则
- 客户端访问服务器的时候,发起查询请求
- 业务服务器先查询Redis,看数据是否在Redis存在,如果存在直接返回,如果不存在就访问MySQL
那么问题来了如何知道Redis应该存储哪些数据呢?
定期生成
每隔一定的周期,对于访问的数据频次做统计,挑选出访问最高的前N%的数据,这些数据就是热点数据。
比如:用户来搜索引擎中输入一个关键字,有些关键字就是高频出现的,搜索引擎的服务器中可以通过日志的方式记录下来,然后每隔一定的时间将结果统计出来,就可以得到热点数据了。
但是想过没有,如果面对像一个节假日的突发情况,某个词会出现的很频繁,但是过了这段时间,就不会有人搜索这个词。 这种情况下,如果采取了定期生成的方式,是不是就不合适了呢??
所以基于上面的场景,有了实时生成的策略
实时生成
先给缓存设定容量上限(可以通过Redis中的maxmemory参数来设定)
接下来用户的每次查询:
- 如果Redis存在,就直接返回
- 如果Redis不存在,就从数据库中查询,将查询到的结果写入到Redis中
但是,如果缓存达到了上限,就会发出淘汰机制,将一些“相对不热门的”的数据淘汰掉。
通常的淘汰策略有如下几种
- FIFO(First In First Out)先入先出:将缓存时间最久的数据淘汰掉
- LRU(Least Recently Used)淘汰最久没使用的:记录每个key的最近访问时间,将最近的访问时间最老的key淘汰掉
- LFU(Least Frequently Used)淘汰访问次数最少的:记录每个key最近一段时间的访问次数,将访问次数最少的key淘汰掉
- Random随机淘汰:从所有的key中随机淘汰一个key
淘汰策略,用户可以自己实现,当然Redis也提供了内置的淘汰策略,以便直接使用。
Redis的内置淘汰策略如下:
- volatile-lru: 当内存不足以容纳新写入的数据时,从设置了过期时间的key中使用LRU算法进行淘汰
- allkeys-lru:当内存不足以容纳新写入的数据时,从所有的key中使用LRU算法进行淘汰
- volatile-lfu:当内存不足以容纳新写入的数据时,在过期的key中,使用lfu算法进行淘汰
- allkeys-lru:当内存不足以容纳新写入的数据时,从所有的key中使用lfu算法进行淘汰
- volatile-random:当内存不足以容纳新写入的数据时,从所有的key中随机淘汰一个key
- volatile-ttl:在设置了过期时间的key中,根据过期时间来淘汰。 (越早的过期key,越早被淘汰)
- noeviction:这是Redis采用的默认策略,当内存不足的时候,写入操作会报错
缓存预热
什么是缓存预热
- 使用Redis作为MySQL的缓存,当MySQL刚刚启动的时候,或者Redis大批量的key失效以后,由于Redis自身没啥数据,会直接访问到MySQL中,就会对MySQL造成巨大的压力。
所以要将热点数据准备好,直接写入到Redis中。
缓存穿透(Cache penetration)
什么是缓存穿透
- 当一个key在Redis中不存在,并且也不在MySQL中,那么如果这时候并发量很大,就会造成巨大的压力。 这就叫做缓存穿透
为何产生:
- 业务设计的不合理,比如缺少必要的参数校验,导致非法的key进行查询
- 开发/运维误操作:将部分的key删除掉了
- …
如何解决
- 针对要查询的参数进行严格的校验,比如查询的key是用户的手机号,那么就需要校验当前的key是否满足一个合法的手机号的格式
- 针对数据库也不存在的key,也存储到Redis中,比如value就随便设置为一个“”,避免后续频繁的访问数据库
- 在应用程序中使用布隆过滤器,先进行一次判断,降低Redis的压力
缓存雪崩
什么是缓存雪崩
- 短时间内大量的key在缓存上失效,导致数据库压力剧增,甚至是宕机了
为何产生:
大规模的key失效有两种情况
- Redis挂了
- Redis设置了大量的key同时过期了
如何解决?
- 部署高可用的Redis集群,采用完善的监控报警系统
- 不给key设置过期时间 或者 设置过期时间的时候随机添加随机时间因子
缓存击穿(Cache breakdown)
什么是缓存击穿
- 相当于缓存雪崩的特殊场景,针对热点的key,突然过期了,导致大量的请求打到了数据库上,引发了宕机
如何解决?
- 基于统计的方式发现热点key,并设置永不过期
- 进行服务降级,例如采用分布式锁,限制同时请求数据库的并发量。 或者进行分流操作,将流量分到其他的服务器中