什么是缓存
缓存(Cache)是一种用于临时存储数据的机制,目的是提高数据访问速度和系统性能。
核心思路就是把一些常用的数据放到触手可及(访问速度更快)的地方,方便随时读取
缓存是一个相对的概念,比如说,对于硬件的访问速度来说,CPU寄存器>内存>硬盘>网络
那么,cpu寄存器就是内存的缓存,内存是硬盘的缓存,硬盘又是网络的缓存
对于计算机硬件来说,往往访问速度越快的设备,成本越高,存储空间越小, 缓存是更快,但是空间上往往是不足的.因此大部分的时候,缓存只放一些热点数据(访问频繁的数据), 就非常有用了
二八定律
关于"二八定律" 20%的热点数据,能够应对80%的访问场景, 因此只需要把这少量的热点数据缓存起来,就可以应对大多数场景,从而在整体上有明显的性能提升
使用Redis作为缓存
关系型数据库虽然功能强大,但是有一个很大的缺陷,就是性能不高.(换而言之,进行一次查询操作消耗的系统资源较多)
如果访问数据库的并发量比较高,对于数据库的压力是很大的,很容易就会使数据库服务器宕机
如何让数据库能够承担更⼤的并发量呢?核⼼思路主要是两个:
开源:引入更多的机器,部署更多的数据库实例,构成数据库集群(主从复制,分库分表等...)
节流:引入缓存,使用其他的方式保存经常访问的热点数据,从而降低直接访问数据库的请求数量
Redis 就是一个用来作为数据库缓存的常见方案
1.Redis的数据在内存中,访问速度快很多
2.Redis只支持简单的key-value存储,不涉及复杂的关系和限制
客户端访问业务服务器,发起查询请求
业务服务器先查询Redis,看想要的数据是否在Redis中存在
如果已经在Redis中存在了,就直接返回.此时不必访问MySQL了
如果在Redis中不存在,再查询MySQL
缓存是用来加快"读操作"的速度的.如果是"写操作",还是要老老实实写数据库,缓存并不能提高性能
缓存的更新策略
定期生成
每隔一定的周期(比如一天/一周/一个月),对于访问的数据频次进行统计,挑选出访问频次最高的前N% 的数据.
实时生成
先给缓存设定容量上限(可以通过Redis配置文件的 maxmemory 参数设定)
接下来把用户每次查询:
如果在Redis查到了,就直接返回.
如果Redis中不存在,就从数据库查,把查到的结果同时也写入Redis
如果缓存已经满了(达到上限),就触发缓存淘汰策略,把一些"相对不那么热门"的数据淘汰掉. 按照上述过程,持续一段时间之后Redis内部的数据自然就是"热门数据"了.
缓存的淘汰策略
LRU(Least Recently Used):
最近最少使用策略,移除最久未被使用的数据。
LFU(Least Frequently Used):
最少使用频率策略,移除使用频率最低的数据。
FIFO(First In, First Out):
先进先出策略,移除最早加入缓存的数据。
Random随机淘汰:
从所有的key中抽取幸运儿被随机淘汰掉
这里的淘汰策略,我们可以自己实现.当然Redis也提供了内置的淘汰策略,也可以供我们直接使用
noeviction:
- 描述:当内存达到限制时,不会再进行任何删除操作,而是直接返回错误。这是默认策略。
- 适用场景:适用于希望数据持久存在的情况,不适合用作缓存。
allkeys-lru(Least Recently Used):
- 描述:删除最久未使用的键(整个键空间)。
- 适用场景:适用于通用的缓存场景,确保频繁访问的数据保留在内存中。
volatile-lru:
- 描述:删除最久未使用的键(仅限设置了过期时间的键)。
- 适用场景:适用于希望缓存仅限于设置了过期时间的键。
allkeys-random:
- 描述:随机删除键(整个键空间)。
- 适用场景:适用于数据访问模式不可预测或不重要的场景。
volatile-random:
- 描述:随机删除键(仅限设置了过期时间的键)。
- 适用场景:适用于希望缓存仅限于设置了过期时间的键,且数据访问模式不可预测的场景。
volatile-ttl:
- 描述:删除最早将要过期的键。
- 适用场景:适用于希望首先删除即将过期的键,以避免删除新近添加的键。
allkeys-lfu(Least Frequently Used):
- 描述:删除最少使用的键(整个键空间)。
- 适用场景:适用于需要保留访问频率较高的数据的缓存场景。
volatile-lfu:
- 描述:删除最少使用的键(仅限设置了过期时间的键)。
- 适用场景:适用于希望缓存仅限于设置了过期时间的键,并且希望保留访问频率较高的数据的场景。
要设置 Redis 的淘汰策略,可以在 Redis 配置文件(redis.conf
)中修改 maxmemory-policy
选项
后面跟的可以是上面提到的任意一个策略
缓存预热(Cache preheating)
缓存预热是指在系统上线之前,提前将热点数据加载到缓存中,以避免系统刚启动时大量请求直接访问数据库,导致数据库压力过大。
- 手动预加载:通过编写脚本或手动执行查询,将数据加载到缓存中。
- 定时任务:使用定时任务在系统启动时或定期加载热点数据。
- 程序逻辑:在程序初始化时,自动加载常用数据到缓存。
优点:
- 提高系统启动时的性能。
- 减少缓存未命中导致的数据库压力。
缓存穿透 (Cache penetration)
缓存穿透是指查询一个缓存和数据库中都不存在的数据,由于缓存不命中,每次请求都会穿透到数据库,给数据库带来很大压力。
解决方法:
- 缓存空值:对于查询结果为空的数据,也进行缓存,并设置一个较短的过期时间。
- 布隆过滤器:使用布隆过滤器在应用层判断请求的数据是否存在,避免不存在的数据请求穿透到数据库。
- 参数校验:对请求参数进行校验,过滤掉非法的请求。
缓存雪崩(Cache avalanche)
缓存雪崩是指在某一时间段,缓存集中失效或不可用,大量请求直接访问数据库,导致数据库压力骤增甚至宕机。
解决方法:
- 缓存过期时间随机化:在设置缓存过期时间时,增加随机因子,避免大量缓存同时过期。
- 互斥锁:在缓存失效时,使用互斥锁(如分布式锁)控制对数据库的访问,避免大量请求同时访问数据库。
- 双缓存:设置两个缓存,当一个失效时,使用另一个缓存作为备份。
缓存击穿(Cache breakdown)
缓存击穿是指某些热点数据在缓存过期后,有大量并发请求同时访问这些数据,导致请求直接穿透到数据库,造成数据库瞬时压力过大。
解决方法:
- 热点数据永不过期:对于极热点的数据,设置为永不过期,定期手动更新。
- 互斥锁:在缓存失效时,使用互斥锁控制对数据库的访问,只允许一个线程访问数据库并重新设置缓存。
- 提前更新:在缓存即将过期前,提前主动刷新缓存。