文章目录
前言
Redis能做数据库、消息队列和缓存中间件,当作为缓存中间件时,能极大的缓解关系型数据库的访问压力,Redis对于数据的读写没有复杂的索引、约束和各种锁,只需简单的键值映射就可以操控数据
Redis缓存更新策略
定期生成
对固定时间周期内的高频访问数据做统计,取一定比例的高频数据作为热点词放入缓存;缺点很明显,无法获取最近的热点数据,比如最近发生的国际大事,肯定不如一个月内的热搜词高频,它没有放在缓存,只能将访问压力传给数据库
实时生成
对于每个访问的数据:
- 如果存在Redis中,就返回
- 如果不在Redis,就去数据库查,有结果就返回并存入Redis;但是Redis内存有限所以必须实施内存淘汰策略
内存达到极限的淘汰策略
通用淘汰策略:
- FIFO(First In First Out)先进先出:缓存中存在时间最久的淘汰掉
- LRU(Least Recently Used)淘汰最久未使用的:记录每个Key的访问时间,时间最早的淘汰掉
- LFU(Least Frequently Used)淘汰访问次数最少的:记录一个时间段内每个Key的访问次数,访问次数最少的淘汰掉
- Random随机淘汰:随机淘汰Key(不推荐)
Redis 采用的淘汰策略(Redis官网描述):
- 默认策略⬇️
- noeviction:不淘汰key,但是如果你要向达到内存极限的Redis放新key会直接返回错误
- 针对全部key⬇️
- allkeys-lru:least recently used(LRU)
- allkeys-lfu:least frequently used(LFU)
- allkeys-random:随机淘汰多个key(Random)
- 针对有过期时间key⬇️
- volatile-lru:对于设置了过期时间的key,记录每个Key的访问时间,时间最早的淘汰掉
- volatile-lfu:对于设置了过期时间的key,记录一个时间段内每个Key的访问次数,访问次数最少的淘汰掉
- volatile-random:对于设置了过期时间的key,随机淘汰多个key
- volatile-ttl:对于设置了过期时间的key,如果TTL (time to live)到达指定值则淘汰掉
作为缓存中间件可能会遇到的问题
Redis作为缓存中间件的最大作用就是保护数据库不因巨大的访问压力而挂掉,以下是Redis无法起保护作用的情况及解决策略
缓存预热
当Redis第一次对接数据库或大批key失效时,Redis内存数据太少,于是访问压力又回到了数据库。
解决办法 :对Redis进行数据预热,例如离线模式下根据上面的缓存更新策略将数据写入Redis
缓存雪崩
Redis节点故障无法提供服务或大批key同时过期时,大量访问压力于是给到了数据库。
解决办法:建立完善的报警机制(如上一篇的哨兵机制和Redis的故障迁移);设置过期时间时避免让大量key同时过期
缓存穿透
key不存在与Redis和数据库里,但客户端仍然频繁的请求,Redis找不到就去数据库查,数据库需要不断的遍历,可能导致数据库挂掉;这种不合理的请求可能是程序参数校验不完善漏掉了此类key、数据库误删了数据、黑客恶意攻击。
解决办法:完善key的校验;对于数据库也不存在的key,保存在Redis中并设置空value;设置布隆过滤器过滤不存在的key
缓存击穿
有些key被高频访问称之为热点词,如果这个key突然过期,Redis来不及更新缓存,大量的访问就会传递给数据库。
解决办法:对热点词设置较长过期时间;发生此类情况时对数据库的访问使用并发锁,限将达到数据库的请求限流并精简化
Redis的过期策略
Redis的key过期策略:
- 定期清除,定期扫描key,对过期的key进行处理,但每次只扫描一部分key避免清除任务占用太多时间,特别对于单线程的Redis,但是保证不了所有过期key都被删除
- 惰性清除,key过期了但是还没被删除,当Redis要访问这个key的时候,发现过期了,于是将其删除并返回nil给访问方,仍然会有过期key残留,占用内存资源
Redis将上述两个策略结合作为清除key的核心方法 - 网络上传言用定时器来清除过期key,错误的!传言说法的定时器是以keys *的方式来扫描处理,肯定严重制约Redis的性能
过期key清除策略
过期key清理优化策略:
- 采用优先级队列:通过优先级队列来实现并单独开一个线程来监控这个优先级队列。队列里过期时间早的key优先级更高,为了节约资源线程根据队首的key的过期时间设置休眠时间,直到这个key过期前才被唤醒,这期间如果有新key加入队列,立马唤醒这个线程来调整队列元素的优先级,并重新设置休眠时间
- 采用时间轮:采用一种逻辑类似于循环链表的结构

在这个结构里划分为许多个小模块,每个小模块存放一个处理key的任务,创建一个线程来访问每个小模块,任务存放顺序的逻辑也类似于循环链表,过期时间早的放前面,按过期时间依次往后存放,线程也依照这个存放顺序访问每个模块,并从模块中取出处理任务清除对应key;在这个过程中,为了避免占用资源(同时也是为了等待key过期),线程是周期性运行的,访问一个模块等待一定时间再转向下一个模块,当前访问的key未过期则放下,否则执行清除key任务空出该模块位置