目录
前言:
我们在Redis的学习中,逐渐了解到了Redis的用途是可以用来当作内存数据库,缓存,消息队列等,虽然现在消息队列的中间件有了更多的选择,但是仍然不影响Redis在内存数据库和缓存中的地位。
今天我们就来学习Redis作为缓存的时候,需要注意哪些事项以及对应的缓存更新策略。废话不多说,进入主题。
引入缓存
学习Redis作为缓存的时候,我们首先要清楚的是缓存究竟是什么?
缓存(Cache)的定义是:
缓存是存储在高速存取介质中的一份数据副本,目的是为了加速数据访问,减少系统延迟和降低后端负载。
缓存的目的就是为了加快访问,在日常生活中,我们拿浏览器举例子,浏览器就是使用硬盘中的数据充当缓存的,比如从网络中获取到的图片,音视频,字体等,都可以加载到硬盘里面,下次访问的时候就不用再次从网络中获取数据了。这也是为什么有的时候我们会看到浏览器所在的目录多出来一些莫名其妙的东西。
这些文件通常就是浏览器缓存的一部分,如果我们清理了缓存,也不会造成严重问题,只是需要重新加载资源。
缓存的意义主要是提高访问速度,那么Redis的定位就是作为内存级别的缓存,作为硬盘的缓存,当然,上面的浏览器是使用硬盘作为缓存,我们由此可见缓存是无处不在的。你要是分的再细一点,在计算机架构中,CPU内部为了提升处理速度,也引入了多级缓存机制(如L1、L2、L3 Cache),本质上与我们今天讲的缓存思想一致。
对于缓存来说,往往存在一个“二八定律”,即百分之20的数据就可以满足百分之八十的访问需求,那么我们就可以把这个百分之20的数据作为缓存使用,当然了,缓存的空间是比较小的,所以实际中我们还需要根据不同的策略细化一下。
那么Redis的存在通常是作为数据库的缓存,拿我们常学的MySQL来说,我们就可以使用Redis作为MySQL的缓存。主要是因为MySQL是一个关联型的数据库,那么速度慢是它一大缺点,我们就可以使用Redis作为缓存来弥补这个缺点。
我们不妨思考一下,为什么MySQL的速度慢?
1.因为MySQL是一个关联型数据库,执行SQL语句的时候会对SQL语句执行一系列的解析,校验,优化等操作。
2.对于一些复杂逻辑的查询,比如笛卡尔积,多表连接查询,效率自然也不会特别好
3.它的所有数据都是存放在硬盘里面的,硬盘的IO速度并不快,并且还是随机访问
4.如果查询的时候没有命中索引,那么需要对每个表遍历,也会大大的增加IO次数
我们不仅可以从软件上来找到MySQL慢的原因,还可以通过硬件层面的操作找到MySQL慢的原因。
并且还有一个点,因为MySQL“抗压能力”不强,每次服务器处理请求的时候,消耗的硬件资源如果超过了主机的某个部分的临界值,比如内存,网络,硬盘,整个系统就容易宕机,也就是说MySQL在处理高并发量上还是差点意思的。
所以Redis的出现就很好的弥补了这些缺点,通过统计的方式获取到热点数据,访问的时候先走Redis,找不到再走MySQL,那么就能减轻MySQL的很多压力了。
这是一种节流的方案,也可以通过开源的方式,比如引入多个服务器,构成一个服务器集群,这样也能提高并发量,不过成本似乎有点高了~
以上是引入缓存的一个基本话题,我们提到了通过统计的方式获取到热点数据,那么热点数据是如何更新的呢,我们就来分析分析,移步到下面的缓存更新策略~
缓存更新策略
对于缓存更新策略,我们要清楚为什么存在这种更新策略?
热点数据是不会一直“热”下去的,比如春节前后,搜索框里最多的关键词就是“春节晚会”,那么它就是这段时间的热点数据,春节一过,它冷下来之后应该如何更新,用什么数据来替换都是我们思考的点。
在Redis中分为了两大类更新策略,分别是定期生成和实时更新,其中实时更新还更加细分为了四种更新策略。
定期生成
在服务器中,有一个非常重要的东西叫做日志,日志记录的是服务器上进行的各种操作,也可以记录在服务器上出现的数据,那么,我们就可以通过日志把在服务器上出现的数据统计出来。
通过统计次数的方式,获得不同关键词的频率,按照频率进行排序,采用降序的方式取前面百分之二十左右的数据,将它们认为是热点词,接下来就可以把这些热点词放到Redis里面了,不过Redis不是作为缓存必要的软件,像搜狗使用的是自己写的服务器,阿里也是使用阿里自研的tai。
那么上面的这个操作涉及到的数据量是非常庞大的,对于数据工程师来说,他们每天的日常是包含了上面的数据统计工作的。
但是实际上来说,数据量如此庞大,一台机器是可能存不下的,所以需要引入分布式的系统来存储这些日志,当然后续的工作就就是数据工程师的事儿了。
那么基于如此庞大的数据量的情况下,我们应该选择一个合适的时间来统计一次数据,可能是一天,可能是一周,那么这种数据量让人工来实现是不现实的,一般来说都快写一套离线的脚本,比如shell脚本,python脚本等。
对于脚本要实现的功能是,完成统计热点数据的过程,根据热点数据找到对应的value,将对应的数据同步到Redis中,控制存放缓存数据的服务器重启。
那么它的优点就是操作非常简单,写完脚本咱们也就不用管其他的了,整个过程是简单可控的,但是呢,它的实时性不够,是面对突发事件的处理能力差劲,比如春节相关的热词,突然成为热词的时候恰好Redis已经更新了,下次是一周之后,这样的话就会对MySQL造成非常大的压力。
此时就应该引入另一种更新策略了,即实时生成。
实时生成
对于实时生成来说,分为了四种策略,分别是FIFO(First in first out)先进先出,LRU(least recently used)淘汰最久未使用的,LFU(least Frequently used)淘汰访问次数最少的,Random随机淘汰。
即对于实时生成的策略来说,根据了不同的情况分为了四种,其中最不合理的就是Random了,其他的三种会根据实际情况使用,根据时间,根据次数,根据顺序分别更新即可。
而在Redis中对于实时生成也有对应的配置项来选择采取上述的哪种内存更新策略。
策略名称 | 策略英文名 | 描述 | 适用场景示例 |
---|---|---|---|
volatile-lru | Least Recently Used (on volatile keys) | 在设置了过期时间(TTL)的键中,使用最近最少使用(LRU)算法淘汰。 | 只想淘汰设置了过期时间的缓存型数据 |
allkeys-lru | Least Recently Used (on all keys) | 在所有键中(无论是否设置TTL),使用最近最少使用(LRU)算法淘汰。 | 所有数据都可以被淘汰,适合纯缓存场景 |
volatile-lfu | Least Frequently Used (on volatile keys) | 在设置了TTL的键中,使用最少使用频率(LFU)算法淘汰。 | 精确控制热点数据淘汰 |
allkeys-lfu | Least Frequently Used (on all keys) | 在所有键中,使用最少使用频率(LFU)算法淘汰。 | 需要严格基于访问频次管理缓存 |
volatile-ttl | Shortest Time to Live | 在设置了TTL的键中,优先淘汰剩余生存时间(TTL)最短的键。 | 临近过期的数据优先淘汰 |
volatile-random | Random (on volatile keys) | 在设置了TTL的键中,随机淘汰。 | 要求简单快速淘汰,数据一致性不敏感 |
allkeys-random | Random (on all keys) | 在所有键中随机淘汰。 | 紧急释放内存,对淘汰精准性要求低 |
noeviction | No Eviction | 拒绝写入新数据(返回错误),不淘汰任何已存在数据。 | 数据严谨性要求高,不能丢失任何缓存 |
当然我们不用记,它在原来的时间,次数的基础上加入了过期时间,所以分为了volatile/allkeys - lfu/lru。
而最后的noeviction的出现,其实是我们漏说的一种情况,因为服务器最开始启动的时候,实际上是Redis里面是没有任何东西的。
那么往Redis存入数据的时候,势必会有一个点是内存爆满的一个点,那么这个时候我们是拒绝更新还是进行更新,就要看实际情况了。
不过主要还是上面的几种。
缓存注意事项
在缓存中,有常见的四种的问题,分别是缓存预热,缓存穿透,缓存雪崩,缓存击穿,这里我们逐个讲解。
缓存预热
缓存预热是指在系统启动或上线初期,提前将热点数据加载到缓存中。以电商平台为例,若用户访问的数据不在缓存中,请求将直接打到数据库(如 MySQL)。但 MySQL 难以承受高并发请求,容易出现宕机风险,导致系统不可用。
为此,常见做法是在系统启动时或定期将热点数据或基于历史访问记录的数据提前写入缓存,从而有效降低数据库压力,提升系统稳定性和响应速度。
对于首次上线的系统,由于没有历史访问数据,预热的数据通常来源于业务方的预估、配置文件、或者灰度测试阶段的行为统计。例如,在电商平台中,可以提前将首页的推荐商品、活动页的秒杀商品等高频数据写入缓存,从而保障系统初期的稳定性和响应速度。
缓存穿透
缓存穿透指的是:请求的数据既不在缓存中,也不在数据库中,导致每次请求都绕过缓存直达数据库,持续下去会给数据库带来较大压力,甚至可能导致系统雪崩。比如一个key,查询的时候在Redis里面没有,那么正常走到MySQL访问,可是MySQL中也没有,那么一次两次是没有问题的。但是关键是如果连续多次的访问一个并不存在的key,这就会给MySQL带来较大的压力,毕竟有的时候这种不存在的key是会存在多个的。
那么出现这种key的情况有大概三种,第一种是业务设计的不合理,没有正确的校验key,导致非法的key也被查询,第二种是开发的时候误操作了,误删了一批的key也是有可能的,第三种就是黑客攻击了,这种不是太常见,所以第一种是非常典型的,第二种虽然没有那么典型,但是也是导致缓存穿透的一种情况,第三种我们了解即可。
那么对应的解决方案有多种,这里我们介绍两种,第一种解决方案是查到这个key在缓存和数据库都没有的时候就将它存到Redis里面,不过对应的value是空值,这种方案简单有效,能避免数据绕过redis直接访问MySQL,当然,我们也应设置一些过期时间,也不能存太多的无效数据。第二种解决方案是使用布隆过滤器,每次请求的数据的现在布隆过滤器中判断是否存在,如果没有直接拦截即可,它虽然有一定的误判,但是不会漏判,也能极大程度的解决穿透的问题。
缓存雪崩
缓存雪崩指的是短时间内Redis上大规模的key失效了,导致缓存命中率陡然下降,从而给MySQL带来较大的压力,甚至导致MySQL宕机。
导致大规模的key有两种情况,一种是Redis直接挂了,那么数据请求只能走MySQL,这个时候就是非常危险的了,当然,Redis挂了可能指的是redis宕机或者是集群模式下Redis的大规模节点宕机,这种都是Redis挂了导致的缓存雪崩。一种是Redis大量key的过期时间一样,所以导致短时间内大量的key同时失效。
那么对应的解决方法,针对于第一种肯定就只能加强监控报警了,针对第二种就是在设置过期时间的时候添加一些随即因子,主要还是为了避免同一时刻大规模的key过期。
缓存击穿
缓存击穿指的是Redis上的大量热点数据同时过期,是缓存雪崩的一种特殊情况,导致的结果同样还是大量的请求直达MySQL,从而导致系统整体不可用。
那么对于缓存击穿来说,它单从字面上来说还是比较容易和缓存穿透混淆的,但是我们应该清楚穿透是因为key在Redis和MySQL上都没有,击穿是因为大量热点数据过期,导致请求直达MySQL,这两者是完全不一样的概念。
那么我们的解决方法可以分为针对热点数据设置永不过期,或者是通过分布式锁来限制数据库的访问频率,即通过数据库服务降级的方式,来减少数据库承受的并发量。
感谢阅读!