目录
一、简介
其实,redis在实际项目中应用在缓存占了绝大多数场景,缓存能够有效降低访问数据库的压力,避免频繁请求数据库,一定程度上提高了系统的响应速度。一般比如临时性的文件、微信access_token等等都比较适合放在redis缓存中。
缓存工作流程图:
大体步骤:
【a】客户端发起一个请求;
【b】首先我们先去redis缓存中查询是否存在命中的数据,如果redis缓存中存在数据,则直接返回缓存中的数据,如果缓存未命中,那么这时候我们再去请求后端数据库查询;
【c】判断数据库中是否存在数据,如果存在则将数据库中查询出来的数据更新到redis缓存中,并且返回客户端,如果数据库中不存在该数据,则返回null;
【d】客户端接收到响应数据,整个请求结束;
二、缓存方式
- key永不过期
- 设置key过期时间
(1)、不设置过期时间
如果我们不对key设置过期时间,相当于数据永久保留的意思,那么随着时间的推移,必然会造成内存逐渐增大以及缓存中数据与数据库中的数据不同步的问题。
那么如何避免上面所说的两个问题呢?
- 内存逐渐增大:我们可以通过设置redis的最大内存大小,让redis按照配置的策略删除不用的key信息,具体我们可以修改redis.conf如下地方:
# redis最大内存大小
maxmemory 1GB
# 内存释放策略
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select among five behaviors:
# 五种缓存清理策略任选
# Least Recently Used
# 最近最少使用策略移除设置了过期的key
# volatile-lru -> Evict using approximated LRU among the keys with an expire set.
# 最近最少使用策略移除key
# allkeys-lru -> Evict any key using approximated LRU.
# Least Frequently Used
# 根据数据的历史访问频率来淘汰数据
# volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# 随机清除过期的key
# volatile-random -> Remove a random key among the ones with an expire set.
# 随机清除key
# allkeys-random -> Remove a random key, any key.
# 移除最近要过期的key
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# 永不过期,默认策略
# noeviction -> Don't evict anything, just return an error on write operations.
# 默认使用永不过期策略maxme
mory-policy noeviction
参数说明:
maxmemory:最大内存大小,单位字节,当超出了这个限制时Redis会依据maxmemory-policy参数指定的策略来删除不需要的key直到Redis占用的内存小于指定内存
maxmemory-policy:redis删除无效key的策略
(2)、设置过期时间
设置key的过期时间,就涉及到该如何设置过期时间以及过期时间设置多大才合适。同样随着时间的推移,内存也会逐渐变大,所以我们也需要配置一下redis最大内存和key删除的策略(最近最少使用、最近最频繁使用规则等),这里建议key的过期时间不要都设置成一样的,这样的话,在某个时间点,大量的key过期,然后所有请求就直接怼到数据库去了,瞬间炸裂,其实就是避免发生缓存雪崩现象。
注意:对过期时间设置合理范围内的随机值。
三、缓存穿透
缓存穿透,指的就是缓存中以及数据库中都不存在的数据,这个时候有可能是攻击者在尝试攻击网站,导致数据库压力大大增加。
解决方案通常有下面几种:
- 第一种方案:缓存空值key-null。
采用缓存空值的方式,如果从数据库查询的对象为空,也放入缓存,只是设定的缓存过期时间较短,比如设置为60秒,最长不超过5分钟;
- 第二种方案:对请求的参数在业务层进行校验;
- 第三种方案:布隆过滤器
布隆过滤器实际上是一个很常见的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点就是有一定的误判率和删困难。
将所有可能存在的数据哈希到一个足够大的bitmaps,一个一定不存在的数据会被这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
- 第四种方案:黑名单限制服务
当发现redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务
四、缓存雪崩
缓存雪崩,在前面已经大概介绍过,指的就是如果很多key的过期时间都设置成一样,假如都设置成60分钟,那么一个小时之后,所有key都过期,所有请求就都直接到数据库了,导致数据库瞬间压力炸裂。
解决方案:
- 第一种方案:构建多级缓存架构
nginx缓存 + redis缓存 + 其他缓存(ehcache等)
- 第二种方案:使用锁或队列
用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上,但是不适用高并发情况。
- 第三种方案:设置过期标志更新缓存
记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程去后台去更新实际key的缓存;
- 第四种方案:将缓存失效时间分散开
比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的情况;
五、缓存击穿
缓存击穿,指的是某个具体的key可能承担着非常大的并发量,假如刚好到了该key的过期时间,持续续的大并发就穿破缓存,直接请求到数据库,引起数据库压力瞬间增大,造成过大压力。
解决方案:
- 第一种方案:设置key永不过期即可;
- 第二种方案:使用锁
从缓存中获取数据,如果数据为空的话,那么就需要从数据库中查询,但是在查询数据库之前必须先获得锁【比如redis的setnx】。获得锁成功的线程才能执行查询数据库操作、更新缓存里面的数据,更新成功并且释放锁;对于没有获得锁的线程,必须先等待一会,比如休眠几秒钟之后,然后再去尝试从缓存中获取数据,这样就避免了同一时间都去数据库查询数据;
- 第三种方案:预先设置热门数据
在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长;
- 第四种方案:实时调整
现场监控哪些热门数据,实时调整key的过期时长;
六、总结
以上就是关于redis作为缓存服务器的一些配置说明以及缓存穿透、雪崩以及击穿等的概念以及给出了相应的解决方案,在项目中注意以下几点:
- 灵活设置redis的最大内存大小以及key删除策略;
- 灵活配置redis中key的过期时间,不要都配置一样的过期时间,不同类别配置不同过期时间,防止缓存雪崩现象;
- 对热点数据设置key永不过期,防止缓存击穿;
以上只是笔者学习的一些总结,希望能对小伙伴们有一点点帮助,如有不对之处,还请及时指正,相互学习。