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