Redis的内存回收机制主要体现在以下两个方面:

  • 删除到达过期时间的键对象。
  • 内存使用达到maxmemory上限时触发内存溢出控制策略。

过期删除策略

删除策略的目标:在内存占用与CPU占用之间寻找一种平衡,顾此失彼都会造成整体redis性能的下降,甚至引发服务器宕机或
内存泄露。

设置Redis键过期时间

先回顾一下Redis 提供的设置过期时间的命令:

  • EXPIRE :表示将键 key 的生存时间设置为 ttl 秒。
  • PEXPIRE :表示将键 key 的生存时间设置为 ttl 毫秒。
  • EXPIREAT :表示将键 key 的生存时间设置为 timestamp 所指定的秒数时间戳。
  • PEXPIREAT :表示将键 key 的生存时间设置为 timestamp 所指定的毫秒数时间戳。

在Redis内部实现中,前面三个设置过期时间的命令最后都会转换成最后一个PEXPIREAT 命令来完成。

其他相关命令还有:

  • 移除键的过期时间 PERSIST :表示将key的过期时间移除。
  • 返回键的剩余生存时间
    • TTL :以秒的单位返回键 key 的剩余生存时间。
    • PTTL :以毫秒的单位返回键 key 的剩余生存时间。

在Redis内部,每当我们设置一个键的过期时间时,Redis就会将该键带上过期时间存放到一个过期字典中。当我们查询一个键时,Redis便首先检查该键是否存在过期字典中,如果存在,那就获取其过期时间。然后将过期时间和当前系统时间进行比对,比系统时间大,那就没有过期;反之判定该键过期。

此外:

  • 对于字符串类型键,执行set命令会去掉过期时间,这个问题很容易在开发中被忽视

    • 如下是Redis源码中,set命令的函数setKey,可以看到最后执行了removeExpire(db,key)函数去掉了过期时间:
void setKey(redisDb *db, robj *key, robj *val) {if (lookupKeyWrite(db,key) == NULL) {dbAdd(db,key,val);} else {dbOverwrite(db,key,val);}incrRefCount(val);// 去掉过期时间removeExpire(db,key);signalModifiedKey(db,key);
}
  • Redis不支持二级数据结构(例如哈希、列表)内部元素的过期功能,例如不能对列表类型的一个元素做过期时间设置。
  • setex命令作为set+expire的组合,不但是原子执行,同时减少了一次网络通讯的时间。

过期删除策略

通常删除某个key,我们有如下三种方式进行处理

1 定时删除

在设置某个key 的过期时间同时,我们创建一个定时器,让定时器在该过期时间到来时,立即执行对其进行删除的操作。

  • 优点:定时删除对内存是最友好的,能够保存内存的key一旦过期就能立即从内存中删除。
  • 缺点:对CPU最不友好,在过期键比较多的时候,删除过期键会占用一部分 CPU 时间,对服务器的响应时间和吞吐量造成影响。

2 惰性删除(Lazy delete)

设置该key 过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key。

  • 优点:对 CPU友好,我们只会在使用该键时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查。
  • 缺点:对内存不友好,如果一个键已经过期,但是一直没有使用,那么该键就会一直存在内存中,如果数据库中有很多这种使用不到的过期键,这些键便永远不会被删除,内存永远不会释放。从而造成内存泄漏。

3 定期删除

每隔一段时间,我们就对一些key进行检查,删除里面过期的key。

  • 优点:可以通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响。另外定期删除,也能有效释放过期键占用的内存。
  • 缺点:难以确定删除操作执行的时长和频率。
    • 如果执行的太频繁,定期删除策略变得和定时删除策略一样,对CPU不友好。
    • 如果执行的太少,那又和惰性删除一样了,过期键占用的内存不会及时得到释放。
    • 另外最重要的是,在获取某个键时,如果某个键的过期时间已经到了,但是还没执行定期删除,那么就会返回这个键的值,这是业务不能忍受的错误

Redis 使用的过期删除策略

Redis所有的键都可以设置过期属性,内部保存在过期字典中。由于进程内保存大量的键,维护每个键精准的过期删除机制会导致消耗大量的CPU,对于单线程的Redis来说成本过高,因此Redis采用惰性删除和定时任务删除机制实现过期键的内存回收

惰性删除:Redis的惰性删除策略由 db.c/expireIfNeeded 函数实现,所有键读写命令执行之前都会调用 expireIfNeeded 函数对其进行检查,如果过期,则删除该键,然后执行键不存在的操作;未过期则不作操作,继续执行原有的命令。

定期删除:由redis.c/activeExpireCycle 函数实现,函数以一定的频率运行,每次运行时,都从一定数量的数据库中取出一定数量的随机键进行检查,并删除其中的过期键。注意:并不是一次运行就检查所有的库,所有的键,而是随机检查一定数量的键。

定期删除函数的运行频率,在Redis2.6版本中,规定每秒运行10次,大概100ms运行一次。在Redis2.8版本后,可以通过修改配置文件redis.conf 的 hz 选项来调整这个次数。

看上面对这个参数的解释,建议不要将这个值设置超过 100,否则会对CPU造成比较大的压力。

定时任务中删除过期键逻辑采用了自适应算法,根据键的过期比例、使用快慢两种速率模式回收键,流程如下图所示:

内存淘汰策略 (逐出算法)

当Redis所用内存达到maxmemory上限时会触发相应的溢出控制策略。

具体策略受maxmemory-policy参数控制,Redis支持8种策略(有关LFU算法的,是从Redis4.0以后版本才有):

  • noeviction:默认策略,不会删除任何数据,拒绝所有写入操作并返回客户端错误信息(error)OOM command not allowed when used memory,此时Redis只响应读操作。生产一般不会选用
  • allkeys-lru 利用LRU算法移除任何key (不管数据有没有设置超时属性,直到腾出足够空间为止)。
  • allkeys-lfu 利用LRU算法移除任何key (不管数据有没有设置超时属性,直到腾出足够空间为止)
  • volatile-lru:根据LRU算法删除设置了超时属性(expire)的键,直到腾出足够空间为止。如果没有可删除的键对象,回退到noeviction策略。
  • volatile-lfu:根据LFU算法删除设置了超时属性(expire)的键,直到腾出足够空间为止。如果没有可删除的键对象,回退到noeviction策略。
  • allkeys-random 无差别的随机移除,直到腾出足够空间为止。
  • volatile-random:随机删除过期键,直到腾出足够空间为止。
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。

在redis.conf 配置文件中,可以设置淘汰方式:

内存溢出控制策略可以采用config set maxmemory-policy{policy}动态配置。Redis支持丰富的内存溢出应对策略,可以根据实际需求灵活定制,比如当设置volatile-lru策略时,保证具有过期属性的键可以根据LRU剔除,而未设置超时的键可以永久保留。还可以采用allkeys-lru策略把Redis变为纯缓存服务器使用。当Redis因为内存溢出删除键时,可以通过执行info stats命令查看evicted_keys指标找出当前Redis服务器已剔除的键数量。

LFU

LFU 算法(Least Frequently Used,最不经常使用):淘汰最近一段时间被访问次数最少的数据,以次数作为参考。

需要指出的是 : LRU 算法或者 TTL 算法都是不是很精确算法,而是一个近似的算法。 Redis 不会通过对全部的键值对进行比较来确定最精确的时间值,从而确定删除哪个键值对 , 因为这将消耗太多的时间 , 导致回收垃圾执行的时间太长 , 造成服务停顿。

当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。这时使用LFU可能更好点

LRU

LRU算法, 最近最久未使用算法, Least Recently Used

下图是一个淘汰的流程:

在Redis中LRU算法是一个近似算法,默认情况下,Redis随机挑选5个键,并且从中选取一个最近最久未使用的key进行淘汰,在配置文件中可以通过maxmemory-samples的值来设置redis需要检查key的个数,但是检查的越多,耗费的时间也就越久,但是结构越精确(也就是Redis从内存中淘汰的对象未使用的时间也就越久),设置多少,综合权衡吧~~

Redis 3.0对这个近似算法的优化

新算法会维护一个候选池(大小为16),池中的数据根据访问时间进行排序,第一次随机选取的key都会放入池中,随后每次随机选取的key只有在访问时间小于池中最小的时间才会放入池中,直到候选池被放满。当放满后,如果有新的key需要放入,则将池中最后访问时间最大(最近被访问)的移除。当需要淘汰时,需要从池中捞出最久没被访问的key淘汰掉就行了。

新旧算法的对比

下面的图片是Redis官方文档给出的新旧算法对比结果:

  • 浅灰色是被淘汰的数据
  • 灰色是没有被淘汰掉的老数据
  • 绿色是新加入的数据

可以看到3.0的效果明显比2.8的要得多,并且取样数越大,越接近标准的LRU算法

为什么Redis不使用真正的LRU ?

原因很简单,理论的LRU需要你占用更大的内存(每个key还需要保存前后key的地址), 但你从上图就可以看出Redis 3.0使用的近似LRU算法使用起来的效果几乎与理论的LRU等效了。

java实现LRU ?

Java自带的集合框架非常强大,实现LRU算法可以直接使用LinkedHashMap集合框架,简单实现的话,只需要重写 removeEldestEntry 方法即可。

import java.util.LinkedHashMap;
import java.util.Map.Entry;public class LRUCache extends LinkedHashMap {private static final long serialVersionUID = 1L;private final int capacity;private long accessCount = 0;private long hitCount = 0;public LRUCache(int capacity) {super(capacity+1, 1.1f, true);this.capacity = capacity;}public String get(String key) {accessCount++;if (super.containsKey(key)) {hitCount++;}String value = (String)super.get(key);return value;}public boolean containsKey(String key) {accessCount++;if (super.containsKey(key)) {hitCount++;return true;} else {return false;}}protected boolean removeEldestEntry(Entry eldest) {return size() > capacity;}public long getAccessCount() {return accessCount;}public long getHitCount() {return hitCount;}
}

这是LinkedHashMap的一个构造函数,传入的第三个参数accessOrder为true的时候,就按访问顺序对LinkedHashMap排序,为false的时候就按插入顺序,默认是为false的。当把accessOrder设置为true后,就可以将最近访问的元素置于最前面。

public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {super(initialCapacity, loadFactor);this.accessOrder = accessOrder;
}

这是LinkedHashMap中另外一个方法,当返回true的时候,就会remove其中最久的元素,可以通过重写这个方法来控制缓存元素的删除,当缓存满了后,就可以通过返回true删除最久未被使用的元素,达到LRU的要求。

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {return false;
}

参考

  • 《Redis 开发与运维》
  • http://antirez.com/news/109
  • https://redis.io/docs/manual/eviction/
  • https://zhuanlan.zhihu.com/p/149528273
  • https://blog.51cto.com/u_15239532/2835914
  • https://www.geekxh.com/1.99.其他补充题目/11.htm
  • https://www.cnblogs.com/ysocean/p/12422635.html
  • https://blog.csdn.net/weixin_43230682/article/details/107670911

Redis 内存回收策略相关推荐

  1. Redis内存回收策略

    Redis 也会因为内存不足而产生错误,也可能因为回收过久而导致系统长期的停顿,因此掌握执行回收策略十分有必要.在 Redis 的配置文件中,当 Redis 的内存达到规定的最大值时,允许配置 6 种 ...

  2. java redis 数据自过期_Java架构-Redis的内存回收策略和Key过期策略,看这篇就够了...

    Redis 作为当下最热门的 Key-Value 存储系统,在大大小小的系统中都扮演着重要的角色,不管是 session 存储还是热点数据的缓存,亦或是其他场景,我们都会使用到 Redis.在生产环境 ...

  3. JVM实战与原理---内存回收策略

    JVM实战与原理 目录 内存回收策略 1. 堆的划分 2. 判断对象是否存活的算法介绍 2.1 引用计数算法 2.2 可达性分析算法 3. 可回收的内存区域清理的算法介绍 3.1 标记-清除算法 3. ...

  4. Redis内存淘汰策略LRU、LFU详解

    Redis内存淘汰原因 Redis是一种内存数据库,redis的容量往往有限,无法存放所有的数据.当内存满了的时候,并且这个时候还需要往Redis中放入新的数据,就需要将Redis中的一部分数据淘汰了 ...

  5. Redis 内存驱逐策略

    1 缓存替代算法 cache algorithms (also frequently called cache replacement algorithms or cache replacement ...

  6. Redis内存回收和持久化策略

    概述 Redis所有数据都存在内存中,在某些时候需要对占用的内存空间进行回收,内存回收主要分为两类,一类是key过期,一类是内存使用达到上限,触发内存淘汰. Key过期策略 要实现key过期,有几种思 ...

  7. Redis-17Redis内存回收策略

    文章目录 概述 maxmemory-policy 参数 主动清理策略 [针对设置了过期时间的key做处理] [ 针对所有的key做处理] [ 不处理 (默认)] 策略选择 maxmemory-samp ...

  8. 浅看Redis内存回收

    1.内存回收 1.1过期key回收 首先由2种方式:惰性回收和周期回收 1.11惰性删除 惰性删除是什么意思?也就是在查询时候再去删除.比方说:对于一个键值对,我们设置了ttl过期时间对吧,等到ttl ...

  9. 记录java对象修改过的字段_Java垃圾回收器与内存回收策略

    Java中,内存由虚拟机管理,控制着回收什么,什么时候回收,怎么回收. 在栈中内存的随线程产生和分配,销毁而回收,在堆中,需要制定一系列策略来判断该回收哪些区域,以及何时回收. 可达性分析 主流的做法 ...

最新文章

  1. Git 头指针分离与 FETCH_HEAD
  2. 使用Python开发的POC多线程批量执行小框架
  3. html excel零不显,Excel数值为0不显示的三种解决方法
  4. 四. python的time和datetime 模块
  5. Codeforces Round #570 (Div. 3)
  6. python decorator. decorator_Python中decorator使用实例
  7. java拷贝文件夹和删除文件夹
  8. linux下php、apache、mysql、curl环境搭建
  9. Zabbix agent 在windows上安装部署
  10. 基于数码相机拍照图像分析的植被覆盖率(FVC)计算软件
  11. 什么是Service(服务)?
  12. 告别996之Stream学习总结
  13. 算法复杂度 hdu1425
  14. 剑指offer刷题记录(上)
  15. Windows10系统蓝屏解决方案
  16. cypress 自动化测试
  17. 还在一直使用Photoshop吗?在线PS处理图片更简单方便
  18. MySQL 如何定时备份数据库
  19. 和尚头上应该点几个点?
  20. 将element-plus 默认的使用英语改成其他语言设置

热门文章

  1. linux中如何实现动态域名解析
  2. 行测-图形推理-6-相似图形类
  3. 百万格子网站(成就百万富翁的梦想)
  4. 18000-6c协议阅读笔记
  5. WebGL绘制3D文字(非中文)
  6. android22 system,有用的Android22一键ROOT权限(中兴U880为例)详细方法..doc
  7. ffmpeg 集成pytorch机器学习完成SDR转HDR视频
  8. 三菱plc搭配四轴雅马哈机械手在线检测收料案例程序
  9. codeforces1404C Fixed Point Removal
  10. android data 加密默认关闭