注:本文主要参考自《Redis设计与实现》
https://www.cnblogs.com/xuliangxing/p/7151812.html
https://www.cnblogs.com/sunsing123/p/11093038.html
http://www.imooc.com/article/257065?block_id=tuijian_wz
1、设置过期时间
所谓过期,就是我们插入数据的时候设置了过期时间,一班情况下有下面两种设置g
- expire key time(以秒为单位)--这是最常用的方式
- setex(String key, int seconds, String value)--字符串独有的方式
注意:
- 除了字符串自己独有设置过期时间的方法外,其他方法都需要依靠expire方法来设置时间
- 如果没有设置时间,那缓存就是永不过期
- 如果设置了过期时间,之后又想让缓存永不过期,使用persist key
2、两种常用的过期策略
对于上面咱们设置了过期时间的数据,那数据到期了咱们怎么删除呢?
-
惰性删除
- 含义:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。
- 优点:删除操作只发生在从数据库取出key的时候发生,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步(如果此时还不删除的话,我们就会获取到了已经过期的key了)
- 缺点:若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,那么可能发生内存泄露(无用的垃圾占用了大量的内存)
-
定期删除
- 含义:每隔一段时间执行一次删除过期key操作
- 优点:
通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用--处理"定时删
定期删除过期key--处理"惰性删除"的缺点 - 缺点
在CPU时间友好方面,不如"惰性删除",因为会导致每隔一段时间删除大量key,占用cpu - 难点
合理设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除)(这个要根据服务器运行情况来定了)
注意:
- 对于主从redis结构的从结点slave的过期key处理
slave的key不会过期,只会等待master的key过期
。如果master的key过期,或者通过LRU淘汰了key,那么会发送一条模拟的del命令给slave
上边所说的数据库指的是内存数据库,默认情况下每一台redis服务器有16个数据库(关于数据库的设置,看下边代码),默认使用0号数据库,所有的操作都是对0号数据库的操作,关于redis数据库的存储结构,查看 第八章 Redis数据库结构与读写原理
memcached只是用了惰性删除,而redis同时使用了惰性删除与定期删除,这也是二者的一个不同点(可以看做是redis优于memcached的一点)
对于惰性删除而言,并不是只有获取key的时候才会检查key是否过期,在某些设置key的方法上也会检查(eg.setnx key2 value2:该方法类似于memcached的add方法,如果设置的key2已经存在,那么该方法返回false,什么都不做;如果设置的key2不存在,那么该方法设置缓存key2-value2。假设调用此方法的时候,发现redis中已经存在了key2,但是该key2已经过期了,如果此时不执行删除操作的话,setnx方法将会直接返回false,也就是说此时并没有重新设置key2-value2成功,所以对于一定要在setnx执行之前,对key2进行过期检查)
3、Redis最终采用的过期策略
惰性删除+定期删除
- 惰性删除流程
在进行get或setnx等操作时,先检查key是否过期,
若过期,删除key,然后执行相应操作;
若没过期,直接执行相应操作 - 定期删除流程(简单而言,对指定个数个库的每一个库随机删除小于等于指定个数个过期key)
遍历每个数据库(就是redis.conf中配置的"database"数量,默认为16)
默认Reds每秒1次做的事情:
1.测试随机的20个keys进行相关过期检测
2.删除所有已经过期的keys。
3.如果有多于25%的keys过期,重复步奏1
这是一个平凡的概率算法,基本上的假设是,我们的样本是这个密钥控件,并且我们不断重复过期检测,直到过期的keys的百分百低于25%,这意味着,在任何给定的时刻,最多会清除1/4的过期keys。
如果当前库中没有一个key设置了过期时间,直接执行下一个库的遍历
随机获取一个设置了过期时间的key,检查该key是否过期,如果过期,删除key
判断定期删除操作是否已经达到指定时长,若已经达到,直接退出定期删除。
注意:
对于定期删除,在程序中有一个全局变量current_db来记录下一个将要遍历的库,假设有16个库,我们这一次定期删除遍历了10个,那此时的current_db就是11,下一次定期删除就从第11个库开始遍历,假设current_db等于15了,那么之后遍历就再从0号库开始(此时current_db==0)
4、过期策略对RDB和AOF的影响
4.1 RDB对过期key的处理
- 过期key对RDB没有任何影响
- 从内存数据库持久化数据到RDB文件
持久化key之前,会检查是否过期,过期的key不进入RDB文件 - 从RDB文件恢复数据到内存数据库
数据载入数据库之前,会对key先进行过期检查,如果过期,不导入数据库(主库情况)
4.2 AOF对过期key的处理
- 从内存数据库持久化数据到AOF文件:
- 当key过期后,还没有被删除,此时进行执行持久化操作(该key是不会进入aof文件的,因为没有发生修改命令)
- 当key过期后,在发生删除操作时,程序会向aof文件追加一条del命令(在将来的以aof文件恢复数据的时候该过期的键就会被删掉)
- AOF重写
重写时,会先判断key是否过期,已过期的key不会重写到aof文件
5. redis内存淘汰机制
内存淘汰机制
Redis 的内存占用会越来越高。Redis 为了限制最大使用内存
,提供了 redis.conf 中的配置参数 maxmemory。
当内存超出 maxmemory,Redis 提供了几种内存淘汰机制让用户选择,配置 maxmemory-policy:
- noeviction:当内存超出 maxmemory,写入请求会报错,但是删除和读请求可以继续。(有些把缓存当数据库用的会用这个策略)
- allkeys-lru:当内存超出 maxmemory,在
所有的 key
中,移除最少使用的key。只把 Redis 既当缓存是使用这种策略。(推荐
)。 - allkeys-random:当内存超出 maxmemory,在
所有的 key
中,随机移除某个 key。(应该没人用吧) - volatile-lru:当内存超出 maxmemory,在
设置了过期时间 key
的字典中,移除最少使用的 key。把 Redis 既当缓存,又做持久化的时候使用这种策略。 - volatile-random:当内存超出 maxmemory,在
设置了过期时间 key
的字典中,随机移除某个key。 - volatile-ttl:当内存超出 maxmemory,在
设置了过期时间 key
的字典中,优先移除 ttl 小的,也就是移除马上就要过期的key。
redis 中的默认的过期策略是volatile-lru 。设置方式
可以通过命令直接设置 config set maxmemory-policy xxxx(eg:volatile-lru)
6.LRU 算法
实现 LRU 算法除了需要 key/value 字典外,还需要附加一个链表,链表中的元素按照一定的顺序进行排列。当空间满的时候,会踢掉链表尾部的元素。当字典的某个元素被访问时,它在链表中的位置会被移动到表头。所以链表的元素排列顺序就是元素最近被访问的时间顺序。
这里基于linkhashmap实现一个lru算法,可设置最大容量,淘汰最老的数据
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int CACHE_SIZE;
public LRUCache(int size) {
//true代表顺序存放,最新的放头部,最老的放尾部
super((int) Math.ceil(size / 0.75) + 1, 0.75f, true);
CACHE_SIZE = size;
}
/**
* 定义移除最老的数据的规则
*
* @author zyh
* @date 2021/8/4
**/
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > CACHE_SIZE;
}
}
```t