一是内存爆了要用 LRU、LFU、FIFO 清理,否则磁盘SWAP,性能急剧下降
二是超时键过期要删除,用主动或惰性的方法
通用过期策略
-
定时过期:
每个设置过expire时间的key都要创建一个定时器,到期立即清除
该策略可以立即清除过期的数据,对内存很友好
但占用大量CPU资源处理过期数据,从而影响响应时间和吞吐量
-
惰性过期:
访问key才会判断该key是否已过期,过期则清除
该策略可最大化节省CPU资源,却对内存非常不友好
极端情况可能出现大量过期key没有再次被访问,而不会被清除,占用大量内存
-
定期过期:
每隔一定的时间,扫描一定数量expires字典中一定数量的key,并清除其中已过期的key
该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可在不同情况下使CPU和内存资源达到最优的平衡效果
(expires字典,key指向键的指针,value是该键的毫秒精度UNIX时间戳)
Redis同时使用惰性过期和定期过期两种
Redis 数据淘汰策略
-
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用 的数据淘汰
-
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数 据淘汰
-
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据 淘汰
-
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
-
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
-
no-enviction(驱逐):禁止驱逐数据
实现逻辑
redis
2.6 随机找三条记录出来,比较哪条空闲时间最长就删哪条,再随机三条出来,一直删到内存足够
3.0 改进:每次随机五条记录出来,插入到一个长度为十六的按空闲时间排序的队列里,排头的那条删掉,再找五条出来,继续尝试插入队列,不只在一次随机的五条里面找最久那条,会连同之前的一起做比较……
Memcached
中规中矩的,用了教科书式的双向链表存储 slab 内的 LRU 关系
元素插入时把自己放到列头,删除时把自己的前后两个元素对接起来,更新时先做删除再做插入
分配内存超限时,从 LRU 的队尾开始清理
Guava Cache
同 Memcached
Ehcache
随机 8 条记录,找出最旧那条,刷到磁盘里
redis 默认设置都是 noeviction,内存不够直接报错,懒得建个双向链表,而且每次访问时都要更新它
redis
主线程 100 毫秒执行一次,随机抽 20 条 Key ,1/4 过期,证明此时过期的键可能比较多,就不等 100 毫秒,立刻开始下一轮的检查
限定每轮的总执行时间不超过 1 毫秒优化CPU占用
Memcached
LRU 爬虫线程,利用之前LRU 队列,可设置多久跑一次 (默认也是 100 毫秒),沿着列尾一直检查过去,每次检查 N 条数据。但没有 Redis 过期的多了立马展开下一轮检查的功能,需要自己自己权衡 N 的设置
Guava Cache
检查调用时机并不是 100 毫秒什么的,而是每次各种写入数据时的 preWriteCleanup()
Ehcache
只有惰性检查,没有主动检查过期的,只会在内存超限时不断用近似 LRU 算法把内存中的元素刷到磁盘中
文件存储中才有超时检查的线程
适用场景
-
allkeys-lru
一部分访问频率较高,其余部分访问频率较低
或无法预测数据使用频率
-
allkeys-random
数据访问概率大致相等
如果研发者需要通过设置不同的ttl来判断数据过期的先后顺序,此时可以选择volatile-ttl策略。
-
volatile-lru或volatile-random
希望一些数据能长期被保存,而一些数据可以被淘汰掉
-
allkeys-lru
设置expire会消耗额外内存,allkeys-lru就可以不再设置过期时间,高效利用内存
lru淘汰根据 maxmemory-samples