redis过期策略以及内存淘汰机制(理论+配置)

一、redis的过期策略:

redis的过期策略是:定期删除+惰性删除
redis在存储数据时,可能会设置过期时间,而所谓的定期删除,指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的key进行检查,如果过期了就会删除。
至于为啥是每隔100ms随机抽取一些数据进行检查而不是全部检查,这就与cpu负载有关了,如redis中的数据十分庞大,并且全部都设置了过期时间,依次全部检查并且进行删除的话负载太高,影响性能。
但是,由于是随机抽取的key进行检查进行删除,那么很多的key可能会到了过期时间了还没进行删除,那么怎么办呢?这时候,惰性删除就会发挥作用了,所谓的惰性删除,就是在读取某个key的时候,redis会先检查一个该key是否过期,如果过期了,就会在此时删除,然后不会给你返回任何东西。
但是此时就会产生另外一个问题,假如一些key设置了过期时间,而定期删除的随机抽取没有选中这些key,而恰好也没有人去获取这些key,惰性删除也发挥不了作用了,那么这些数据就会越积累越多,redis一般作为缓存的,是基于内存的,这些数据越来越多的时候回导致内存耗尽,影响性能,这时候应该怎么办呢?这时候,另一个重量型的武器就要发挥作用了,那就是:内存淘汰机制


 二、内存淘汰机制:

redis 内存淘汰机制(内存淘汰策略)有以下几个:
• noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
• allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
• allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
• volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
• volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
• volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。

但是理论性的东西我们明白了,那么该如何配置内存淘汰策略呢?


三、内存淘汰策略的配置

我们通过配置redis.conf中的maxmemory这个值来开启内存淘汰功能。

# maxmemory

值得注意的是,maxmemory为0的时候表示我们对Redis的内存使用没有限制。

根据应用场景,选择淘汰策略

# maxmemory-policy noeviction

我们来了解下,内存淘汰的过程

首先,客户端发起了需要申请更多内存的命令(如set)。

然后,Redis检查内存使用情况,如果已使用的内存大于maxmemory则开始根据用户配置的不同淘汰策略来淘汰内存(key),从而换取一定的内存。

最后,如果上面都没问题,则这个命令执行成功。

动态改配置命令

此外,redis支持动态改配置,无需重启。

设置最大内存

config set maxmemory 100000

设置淘汰策略

config set maxmemory-policy noeviction

如何选择淘汰策略

下面看看几种策略的适用场景

allkeys-lru:如果我们的应用对缓存的访问符合幂律分布,也就是存在相对热点数据,或者我们不太清楚我们应用的缓存访问分布状况,我们可以选择allkeys-lru策略。

allkeys-random:如果我们的应用对于缓存key的访问概率相等,则可以使用这个策略。

volatile-ttl:这种策略使得我们可以向Redis提示哪些key更适合被eviction。

另外,volatile-lru策略和volatile-random策略适合我们将一个Redis实例既应用于缓存和又应用于持久化存储的时候,然而我们也可以通过使用两个Redis实例来达到相同的效果,值得一提的是将key设置过期时间实际上会消耗更多的内存,因此我们建议使用allkeys-lru策略从而更有效率的使用内存。


四、设置过期时间

redis有四种命令可以用于设置键的生存时间和过期时间:

EXPIRE <KEY> <TTL> : 将键的生存时间设为 ttl 秒
PEXPIRE <KEY> <TTL> :将键的生存时间设为 ttl 毫秒
EXPIREAT <KEY> <timestamp> :将键的过期时间设为 timestamp 所指定的秒数时间戳
PEXPIREAT <KEY> <timestamp>: 将键的过期时间设为 timestamp 所指定的毫秒数时间戳.

当然,平时我们也可以使用java代码的方式进行过期时间的设置,一些工具类可以直接满足要求,在这里,我就提供一个工具类:

 import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.TimeUnit;

    /**
     * redis的工具类
     * @author zangchuanlei
     * @date 2019.09.18
     */
    @Component
    public final class RedisUtil {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

        /**
         * 指定缓存失效的时间
         * @param key 键
         * @param time 时间(秒)
         * @return
         */
    public boolean expire(String key,long time){
        try {
            if(time > 0){
                redisTemplate.expire(key,time,TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
         * 根据key获取过期时间
         * @param key 键 不能为null
         * @return 时间(秒)返回0代表为永久有效
     */
    public long getExpire(String key){
        return redisTemplate.getExpire(key,TimeUnit.SECONDS);
    }

        /**
         * 判断key是否存在
         * @param key 键
         * @return true 存在 false 不存在
         */
    public boolean hasKey(String key){
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

        /**
         * 删除缓存
         * @param key 可以传一个值或者多个值
         */
    public void del(String... key){
        if(key != null && key.length > 0){
            if(key.length == 1){
                redisTemplate.delete(key[0]);
            }else{
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    //=========================String======================
        /**
         *普通缓存获取
         * @param key 键
         * @return*/
    public Object get(String key){
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

        /**
         * 普通缓存的放入
         * @param key
         * @param value
         * @return
         */
    public boolean set(String key, Object value){
        try {
            redisTemplate.opsForValue().set(key,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

        /**
         * 普通缓存放入并设置时间
         * @param key 键
         * @param value 值
         * @param time 时间(秒) time要大于0,如果time要是小于0,将设置无限期
         * @return true成功 false 失败
         */
    public boolean set(String key,Object value,long time){
        try {
            if(time > 0){
                redisTemplate.opsForValue().set(key,value,time,TimeUnit.SECONDS);
            }else{
                set(key,value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

        /**
         * 递增
         * @param key 键
         * @param delta 要增加几(大于0)
         * @return
         */
    public long incr(String key,long delta){
        if(delta < 0){
            throw  new RuntimeException("增强因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key,delta);
    }

        /**
         * 递减
         * @param key 键
         * @param delta 要减少几(大于0)
         * @return
         */
    public long decr(String key,long delta){
        if(delta < 0){
            throw  new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key,-delta);
    }

        /**
         * HashGet
         * @param key 键 不能为null
         * @param item 项 不能为null
         * @return*/
    public Object hget(String key,String item){
        return redisTemplate.opsForHash().get(key,item);
    }

        /**
         * 获取hashKey对应的所有的键值
         * @param key 键
         * @return 对应的多个键值
         */
    public Map<Object,Object> hmget(String key){
        return redisTemplate.opsForHash().entries(key);
    }

        /**
         * hashSet
         * @param key 键
         * @param map 对应多个键值
         * @return true成功 false 失败
         */
    public boolean hmset(String key,Map<String,Object> map){
        try {
            redisTemplate.opsForHash().putAll(key,map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    
        /**
         * HashSet并设置时间
         * @param key 键
         * @param map 对应多个键值
         * @param time 时间秒
         * @return true 成功 false 失败
         */
    public boolean hmset(String key,Map<String,Object> map,long time){
        try {
            redisTemplate.opsForHash().putAll(key,map);
            if(time > 0){
                expire(key,time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

        /**
         * 向一张hash表中放入数据,如果不存在将创建
         * @param key 键
         * @param item 项
         * @param value 值
         * @return true成功 false失败
         */
    public boolean hset(String key,String item,Object value){
        try {
            redisTemplate.opsForHash().put(key,item,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

        /**
         * 向一张hash表中放入数据,如果不存在将创建
         * @param key 键
         * @param item 项
         * @param value 值
         * @param time 时间(秒) 注意:如果已经存在的hash表有时间,这里将会替换原有的时间
         * @return true成功 false 失败
         */
    public boolean hset(String key,String item,Object value,long time){
        try {
            redisTemplate.opsForHash().put(key,item,value);
            if(time > 0){
                expire(key,time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    
        /**
         * 删除hash表中的值
         * @param key 键 不能为null
         * @param item 项 可以使用多个 但不能为null
         */
    public void hdel(String key,Object... item){
        redisTemplate.opsForHash().delete(key,item);
    }

        /**
         * 判断hash表中是否有该项的值
         * @param key 键 不能为null
         * @param item 项 不能为null
         * @return true 存在 false 不存在
         */
    public boolean hHasKey(String key,String item){
        return redisTemplate.opsForHash().hasKey(key,item);
    }

        /**
         * hash递增 如果不存在,就会创建一个 把新增后的值返回
         * @param key 键
         * @param item 项
         * @param by 要增加几(大于0)
         * @return
         */
    public double hincr(String key,String item,double by){
        return redisTemplate.opsForHash().increment(key,item,by);
    }

        /**
         * hash递减 如果不存在,就会创建一个 把新增后的值返回
         * @param key 键
         * @param item 项
         * @param by 要减少几(大于0)
         * @return
         */
    public double hdecr(String key,String item,double by){
        return redisTemplate.opsForHash().increment(key,item,-by);
    }

    //==========================set====================

        /**
         * 根据key获取set中的所有值
         * @param key 键
         * @return
         */
    public Set<Object> sGet(String key){
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
        /**
         * 根据value从一个set中查询,是否存在
         * @param key 键
         * @param value 值
         * @return true存在 false不存在
         */
    public boolean sHashKey(String key,Object value){
        try {
            return redisTemplate.opsForSet().isMember(key,value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

        /**
         * 将数据放入set缓存
         * @param key 键
         * @param values 值 可以是多个
         * @return 成功个数
         */
    public long sSet(String key,Object... values){
        try {
            return redisTemplate.opsForSet().add(key,values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

        /**
         * 将set数据放入缓存并设置失效时间
         * @param key 键
         * @param time 时间(秒)
         * @param values 值 可以是多个
         * @return 成功个数
         */
    public long sSetAndTime(String key,long time,Object... values){
        try {
            Long count = redisTemplate.opsForSet().add(key,values);
            if(time > 0){
                expire(key,time);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

        /**
         * 获取set缓存的长度
         * @param key 键
         * @return
         */
    public long sGetSetSize(String key){
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

        /**
         * 移除值为value的
         * @param key 键
         * @param values 值 可以是多个
         * @return 移除的个数
         */
    public long setRemove(String key,Object... values){
        try {
            Long count = redisTemplate.opsForSet().remove(key,values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    //===================list====================

        /**
         * 获取list缓存的内容
         * @param key 键
         * @param start 开始
         * @param end 结束 0到-1代表所有值
         * @return
         */
    public List<Object> lGet(String key,long start,long end){
        try {
            return redisTemplate.opsForList().range(key,start,end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

        /**
         * 获取list缓存的长度
         * @param key 键
         * @return
         */
    public long lGetListSize(String key){
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

        /**
         * 通过索引 获取list中的值
         * @param key 键
         * @param index 索引 index>=0, 0表头,1 第二个元素 ;
         *              索引index<0时,-1,表尾,-2倒数第二个元素 以此类推
         * @return
         */
    public Object iGetIndex(String key,long index){
        try {
            return redisTemplate.opsForList().index(key,index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

        /**
         * 将list放入缓存
         * @param key 键
         * @param value 值
         * @return
         */
    public boolean lSet(String key,Object value){
        try {
            redisTemplate.opsForList().rightPush(key,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key,Object value,long time){
        try {
            redisTemplate.opsForList().rightPush(key,value);
            if(time > 0){
                expire(key,time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */
    public boolean lSet(String key,List<Object> value){
        try {
            redisTemplate.opsForList().rightPushAll(key,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key,List<Object> value,long time){
        try {
            redisTemplate.opsForList().rightPushAll(key,value);
            if(time > 0){
                expire(key,time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     * @param key 键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key,long index,Object value){
        try {
            redisTemplate.opsForList().set(key,index,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N个值为value
     * @param key 键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key,long count,Object value){
        try {
            Long remove = redisTemplate.opsForList().remove(key,count,value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    } }

 



参考原文链接:https://blog.csdn.net/baidu_26954625/article/details/90648597

posted @ 2020-03-02 16:18  帝莘  阅读(2491)  评论(0编辑  收藏  举报