目录
一、redis缓存过期淘汰策略
1.redis默认内存
如果不设置最大内存大小或者设置最大内存大小为0,在64位操作系统下不限制内存大小,在32位操作系统下最多使用3GB内存。
一般推荐redis设置内存为最大物理内存的四分之三。
2.修改redis内存
①修改配置文件
打开redis配置文件,设置maxmemory参数,maxmemory是bytes字节类型,注意转换。
②通过命令
3.查看redis内存情况
二、redis内存超出了设置的最大值会怎么样
1.设置1个字节之后再赋值,会爆OOM
三、内存淘汰策略
在redis3.0之前,默认是volatile-lru;在redis3.0之后(包括3.0),默认淘汰策略则是noeviction。
1.一个键过期了,是怎么删除的
①定时删除(对CPU不友好,用处理器性能换取存储空间)
Redis不可能时时刻刻遍历所有被设置了过期时间的key,来检测数据是否已经达到过期时间,然后对它进行删除。
立即删除能保证内存中数据的最大新鲜度,因为它保证过期键值会在过期后马上被删除,其所占用的内存也会随之释放。但是立即删除对cpu是最不友好的。因为遍历删除操作会占用大量的cpu时间,会非常影响性能。
②惰性删除(对内存不友好)
数据达到过期时间之后,不做处理,等下次访问该数据时进行判断:
数据未过期,返回数据;数据过期,删除,返回不存在。
惰性删除的缺点是,对内存不友好。
如果一个键已经过期,而这个键又仍然保存在数据库中,那么只要这个过期的键不被删除,它所占用的内存就不会被释放。
如果使用惰性删除策略,恰好某些键并不会被访问到,那么这些键就永远不会被删除。我们甚至可以将这种情况看做是一种内存泄漏,无用的垃圾数据占用了大量的内存,而服务器不会去释放它们,这对于运行状态非常依赖于内存的redis服务器来说,肯定不是一个好消息。
③定期删除(随机抽查,也会有漏网之鱼,会有一直没抽取到的)
定期删除策略是前两种策略的折中:
定期删除策略每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。
周期性轮训redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度。
特点1:CPU性能占用设置有峰值,检测频度可自定义设置;特点2:内存压力不是很大,长期占用内存的冷数据会被持续清理。总而言之,周期性抽查存储空间(随机抽查、重点抽查)。
定期删除策略的难点是确定删除操作执行的时长和频率:如果删除操作执行的太频繁,或者执行的时间太长,定期删除策略就会退化成定时删除策略,以至于将CPU时间过多的消耗在删除过期键上面。如果删除操作执行的太少,或者执行时间太短,定期删除策略又会和惰性删除策略一样,出现浪费内存的情况。因此,如果采用定期删除策略的话,服务器必须根据情况,合理地设置删除操作的执行时长和执行频率。
2.内存淘汰策略
Redis 4.0 之前一共实现了 6 种内存淘汰策略,在 4.0 之后,又增加了 2 种策略。
在redis3.0之前,默认是volatile-lru;在redis3.0之后(包括3.0),默认淘汰策略则是noeviction。
noeviction:不会删除任何key,当内存满了之后直接报OOM错误。
volatile-ttl 在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
volatile-random 就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。
volatile-lru 会使用 LRU 算法筛选设置了过期时间的键值对。
volatile-lfu 会使用 LFU 算法选择设置了过期时间的键值对。
allkeys-random 策略,从所有键值对中随机选择并删除数据;
allkeys-lru 策略,使用 LRU 算法在所有数据中进行筛选。
allkeys-lfu 策略,使用 LFU 算法在所有数据中进行筛选。
lru:删除最近最少使用的
lfu:删除最少频繁使用的的。
一般用allkeys-lru内存淘汰策略。
3.修改内存淘汰策略
①配置文件
②命令
lru算法
是什么:
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的数据予以淘汰。
LRU 算法背后的想法非常朴素:它认为刚刚被访问的数据,肯定还会被再次访问,所以就把它放在 MRU 端;长久不访问的数据,肯定就不会再被访问了,所以就让它逐渐后移到 LRU 端,在缓存满时,就优先删除它。
不过,LRU 算法在实际实现时,需要用链表管理所有的缓存数据,这会带来额外的空间开销。而且,当有数据被访问时,需要在链表上把该数据移动到 MRU 端,如果有大量数据被访问,就会带来很多链表移动操作,会很耗时,进而会降低 Redis 缓存性能。
所以,在 Redis 中,LRU 算法被做了简化,以减轻数据淘汰对缓存性能的影响。具体来说,Redis 默认会记录每个数据的最近一次访问的时间戳(由键值对数据结构 RedisObject 中的 lru 字段记录)。然后,Redis 在决定淘汰的数据时,第一次会随机选出 N 个数据,把它们作为一个候选集合。接下来,Redis 会比较这 N 个数据的 lru 字段,把 lru 字段值最小的数据从缓存中淘汰出去。
Redis 提供了一个配置参数 maxmemory-samples,这个参数就是 Redis 选出的数据个数 N。例如,我们执行如下命令,可以让 Redis 选出 100 个数据作为候选数据集:
CONFIG SET maxmemory-samples 100
当需要再次淘汰数据时,Redis 需要挑选数据进入第一次淘汰时创建的候选集合。这儿的挑选标准是:能进入候选集合的数据的 lru 字段值必须小于候选集合中最小的 lru 值。当有新数据进入候选数据集后,如果候选数据集中的数据个数达到了 maxmemory-samples,Redis 就把候选数据集中 lru 字段值最小的数据淘汰出去。
这样一来,Redis 缓存不用为所有的数据维护一个大链表,也不用在每次数据访问时都移动链表项,提升了缓存的性能。
LRU算法核心是哈希链表
使用LinkedHashMap实现LRU算法
package 作业;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
/*LRU是Least Recently Used 近期最少使用算法。
*通过HashLiekedMap实现LRU的算法的关键是,如果map里面的元素个数大于了缓存最大容量,则删除链表头元素
*/
/*public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder)
*LRU参数参数:
*initialCapacity - 初始容量。
*loadFactor - 加载因子(需要是按该因子扩充容量)。
*accessOrder - 排序模式( true) - 对于访问顺序(get一个元素后,这个元素被加到最后,使用了LRU 最近最少被使用的调度算法),对于插入顺序,则为 false,可以不断加入元素。
*/
/*相关思路介绍:
* 当有一个新的元素加入到链表里面时,程序会调用LinkedHahMap类中Entry的addEntry方法,
*而该方法又会 会调用removeEldestEntry方法,这里就是实现LRU元素过期机制的地方,
* 默认的情况下removeEldestEntry方法只返回false,表示可以一直表链表里面增加元素,在这个里 *修改一下就好了。
*
*/
/*
测试数据:
5
11
4 7 0 7 1 0 1 2 1 2 6
*/
import java.util.*;
public class LRULinkedHashMap<K,V> extends LinkedHashMap<K,V>{
private int capacity; //初始内存容量
LRULinkedHashMap(int capacity){ //构造方法,传入一个参数
super(16,0.75f,true); //调用LinkedHashMap,传入参数 ,true是按照访问顺序,false是按照插入顺序排
this.capacity=capacity; //传递指定的最大内存容量
}
@Override
public boolean removeEldestEntry(Map.Entry<K, V> eldest){
//,每加入一个元素,就判断是size是否超过了已定的容量
System.out.println("此时的size大小="+size());
if((size()>capacity))
{
System.out.println("超出已定的内存容量,把链表顶端元素移除:"+eldest.getValue());
}
return size()>capacity;
}
public static void main(String[] args) throws Exception{
Scanner cin = new Scanner(System.in);
System.out.println("请输入总共内存页面数: ");
int n = cin.nextInt();
Map<Integer,Integer> map=new LRULinkedHashMap<Integer, Integer>(n);
System.out.println("请输入按顺序输入要访问内存的总共页面数: ");
int y = cin.nextInt();
System.out.println("请输入按顺序输入访问内存的页面序列: ");
for(int i=1;i<=y;i++)
{
int x = cin.nextInt();
map.put(x, x);
}
System.out.println("此时内存中包含的页面数是有:");
//用for-each语句,遍历此时内存中的页面并输出
for(java.util.Map.Entry<Integer, Integer> entry: map.entrySet()){
System.out.println(entry.getValue());
}
}
}