前言

redis是一个内存数据库。
我们set key的时候,都可以给一个expire time,就是过期时间。
如果假设你设置一批key只能存活1个小时,那么接下来1小时后,redis是怎么对这批key进行删除的?
redis内存满了之后会发生什么事情呢?
生产上应该设置哪种缓存淘汰策略呢?
好了,今天我们就来讲讲redis的缓存淘汰策略。


redis三种删除策略

1.定时删除

定时删除也就是立即删除。

在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除。

立即删除能够保证内存被尽快释放。但是这样的话对cpu很不友好,因为删除操作会占用cpu时间,所以如果过期的key很多,删除这些key会占用很多的CPU时间,在CPU时间紧张的情况下,也会影响数据的读写操作。

总结:用cpu性能换取内存空间(时间换空间)。

2.惰性删除

当数据到达过期时间时,先不做处理。等到下次访问该数据时,如果数据已过期,再对数据进行删除。

这种删除策略问题也很明显,如果内存中有大量过期key。但是一直没人访问,那么数据就不会过期,内存也不会释放。发生内存泄露。

总结:用内存换取cpu处理时间(空间换时间)。

3.定期删除

定时删除是对CPU和内存消耗取得一个折中方案。

每隔一段时间执行一次删除过期key操作。
通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用(处理"定时删除"的缺点)
定期删除过期key(处理"惰性删除"的缺点)

难点
合理设置删除操作的执行时长和执行频率(要根据具体服务器运行情况来定)

redis使用的哪种过期策略?

定期删除+惰性删除

redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。

上面说过定期删除可能会导致很多过期key到了时间并没有被删除掉。所以就用到了惰性删除了,也就是说,在你获取某个key的时候,redis会检查一下 ,这个key如果过期了此时就会删除,不会给你返回任何东西。

通过上述两种手段结合起来,保证过期的key一定会被干掉。

但是这样还是有问题的,如果定期删除漏掉了很多过期key,然后你也没及时去查,也就没走惰性删除,此时大量过期key堆积在内存里,导致redis内存快耗尽了,咋整?

下面就轮到redis的内存淘汰机制了。

redis内存淘汰策略

当redis的内存占用过多的时候,此时会进行内存淘汰,redis6以后有如下一些策略

规则名称

规则说明

noeviction

当内存不足以容纳新写入数据时,新写入操作会报错

allkeys-lru

对所有key使用lru算法进行删除

allkeys-lfu

对所有key使用lfu算法进行删除

allkeys-random

对所有key随机删除key

volatile-lru

对设置了过期时间的key使用lru算法进行删除

volatile-lfu

对设置了过期时间的key使用lfu算法进行删除

volatile-random

对设置了过期时间的key使用随机删除

volatile-ttl

删除快要过期的key

LRU算法

LRU算法又叫淘汰算法,根据数据历史访问记录进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

即删除最近最少使用的缓存。

我们用java实现一个demo看看

package com.fandf.test.redis;  
  
import lombok.extern.slf4j.Slf4j;  
  
import java.util.HashMap;  
import java.util.LinkedList;  
import java.util.Map;  
  
/**  
* lru算法demo  
* 最近最少使用算法  
*  
* @author fandongfeng  
* @date 2023/4/2 17:30  
*/  
@Slf4j  
public class LRUCache<K, V> {  
  
    private final Map<K, V> cache = new HashMap<>(8);  
    private final LinkedList<K> lruList = new LinkedList<>();  
    private final Integer capacity;  
  
    public LRUCache(Integer capacity) {  
        this.capacity = capacity;  
    }  
  
    public static void main(String[] args) {  
        test();  
    }  

    private static void test() {  
        LRUCache<String, Object> cache = new LRUCache<>(2);  
        cache.put("第一天", "好好学技术");  
        cache.put("第二天", "我想出去玩");  
        log.info("第一天数据 => {}", cache.get("第一天"));  
        log.info("第二天数据 => {}", cache.get("第二天"));  
        cache.put("第三天", "学不动了");  
        //这里调用了 cache.get("第二天"),所以第二天变为最新使用过得缓存  
        log.info("放入第三天数据,第二天数据 => {}", cache.get("第二天"));  
        //这里调用了cache.put("第四天", "马什么梅?");,所以最后只剩了第二天和第四天  
        cache.put("第四天", "马什么梅?");  
        log.info("放入第四天数据");  
        log.info("第一天数据 => {}", cache.get("第一天"));  
        log.info("第二天数据 => {}", cache.get("第二天"));  
        log.info("第三天数据 => {}", cache.get("第三天"));  
        log.info("第四天数据 => {}", cache.get("第四天"));  
    }  
  
    /**  
    * 获取元素  
    *  
    * @param k key  
    * @return value  
    */  
    public V get(K k) {  
        if (!cache.containsKey(k)) {  
            return null;  
        }  
        V v = cache.get(k);  
        // 更新 k 在LRU 队列中的位置  
        if (!lruList.getFirst().equals(k)) {  
            //移除K,在放入队头  
            lruList.remove(k);  
            lruList.addFirst(k);  
        }  
        return v;  
    }  
  
    /**  
    * 存入元素  
    *  
    * @param k Key  
    * @param v value  
    */  
    public void put(K k, V v) {  
        // 判断容量  
        if (lruList.size() >= capacity && !lruList.contains(k)) {  
        // 移除最近最少使用的数据  
        cache.remove(lruList.getLast());  
            lruList.removeLast();  
        }  
        cache.put(k, v);  
        //放入队头  
        if (!lruList.contains(k)) {  
            lruList.addFirst(k);  
        }  
    }  
  
}

缺点:假设我们有两个key, key1和key2,我访问了一万次key1,然后访问了一次key2,此时如果使用lru算法,那么就会将key2淘汰。

LFU算法

LFU(Least Frequently Used)是Redis 4.0 引入的淘汰算法,它通过key的访问频率、访问时间比较来淘汰key,重点突出的是Frequently Used。

LFU算法的思想是:如果一个数据在最近一段时间很少被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最小频率访问的数据最先被淘汰。

应该使用哪种?

redis默认的内存淘汰策略是noeviction,即内存不足时,写入直接报错。这种方式往往会导致服务不可用,但是往往即使是生产环境,很多人在使用的时候都不会修改内存淘汰策略。

生产上建议根据情况选择不同的策略使用。如果不知道使用哪种?个人建议还是使用allkeys-lru吧。怎么说呢,中规中矩吧。