前言

关于 Redis 的“起承转合”,我前面已经用五个篇章的长度作了一个 Redis 基础篇——“起”篇的详细阐述,相信大家无论之前有没有接触过 Redis,都能从中学到不少东西。基础篇的内容顾名思义,只是个基础,主要说了 Redis 的发展以及 Redis 的基本数据类型,内容跟平时使用关联会比较大,难度不算大,希望大家能好好消化。 这里送上基础篇的飞机票:

【起】Redis 概述篇——带你走过 Redis 的前世今生

【起】Redis 基础篇——基本数据结构之String,Hash

【起】Redis 基础篇——基本数据结构之 List,Set

【起】Redis 基础篇——基本数据结构之 ZSet,Bitmap…

【起】Redis 基础篇——基本数据结构之总结篇

在“承”篇中,我会围绕 Redis 的原理来阐述,讲一些相对比较高级的特性,比如本篇章要讲到的 pub/sub(发布/订阅)模式,持久化机制,高性能特性,事务,内存回收机制等等,在接下来的篇章中,我会为大家穿针引线,把每个篇章的内容都串起来,这里就先不占用大家的前言篇章。

那话归正题,我们今天来看一下关于 Redis 的内存回收机制。

正文

内存回收

Reids 所有的数据都是存储在内存中的,在某些情况下需要对占用的内存空间进行回收。内存回收主要分为两类,一类是 key 过期,一类是内存使用达到上限(max_memory)触发内存淘汰。

过期策略

要实现 key 过期,我们有几种思路。

定时过期(主动淘汰)

每个设置过期时间的 key 都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的 CPU 资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。

惰性过期(被动淘汰)

只有当访问一个 key 时,才会判断该 key 是否已过期,过期则清除。该策略可以最大化地节省 CPU 资源,却对内存非常不友好。极端情况可能出现大量的过期 key 没有再次被访问,从而不会被清除,占用大量内存。

例如 String,在 getCommand 里面会调用 expireIfNeeded

server.c    expireIfNeeded(redisDb *db, robj *key)复制代码

第二种情况,每次写入 key 时,发现内存不够,调用 activeExpireCycle 释放一部分内存。

expire.c    activeExpireCycle(int type)复制代码

定期过期

源码:server.h

typedef struct redisDb {            dict *dict; /*  所有的键值对 */    dict *expires;  /*  设置了过期时间的键值对 */    dict *blocking_keys;    /* Keys with clients waiting for data (BLPOP)*/    dict *ready_keys;   /* Blocked keys that received a PUSH */    dict *watched_keys;     /* WATCHED keys for MULTI/EXEC CAS */    int id; /* Database ID */    long long avg_ttl;  /* Average TTL, just for stats */    list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */} redisDb;      复制代码

每隔一定的时间,会扫描一定数量的数据库的 expires 字典中一定数量的 key,并清除其中已过期的 key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得 CPU 和内存资源达到最优的平衡效果。

Redis 中同时使用了惰性过期和定期过期两种过期策略。

如果都不过期,Redis 内存满了怎么办?

淘汰策略

Redis 的内存淘汰策略,是指当内存使用达到最大内存极限时,需要使用淘汰算法来决定清理掉哪些数据,以保证新数据的存入。

最大内存设置

redis.conf 参数配置:

#maxmemory 复制代码

如果不设置 maxmemory 或者设置为 0,64 位系统不限制内存,32 位系统最多使用 3GB 内存。

动态修改:

redis> config set maxmemory 2GB复制代码

到达最大内存以后怎么办?

淘汰策略

redis.io/topics/lru-…

redis.conf

#maxmemory-policy noeviction#volatile-lru -> Evict using approximated LRU among the keys with an expire set.#allkeys-lru -> Evict any key using approximated LRU.#volatile-lfu -> Evict using approximated LFU among the keys with an expire set.#allkeys-lfu -> Evict any key using approximated LFU.#volatile-random -> Remove a random key among the ones with an expire set.#allkeys-random -> Remove a random key, any key.#volatile-ttl -> Remove the key with the nearest expire time (minor TTL)#noeviction -> Don't evict anything, just return an error on write operations.复制代码

先从算法来看:

LRU,Least Recently Used:最近最少使用。判断最近被使用的时间,目前最远的数据优先被淘汰。

LFU,Least Frequently Used,最不常用,4.0 版本新增。

random,随机删除。

策略含义volatile-lru根据 LRU 算法删除设置了超时属性(expire)的键,直到腾出足够内存为止。如果没有可删除的键对象,回退到 noeviction 策略。allkeys-lru根据 LRU 算法删除键,不管数据有没有设置超时属性,直到腾出足够内存为止。volatile-lfu在带有过期时间的键中选择最不常用的。allkeys-lfu在所有的键中选择最不常用的,不管数据有没有设置超时属性。volatile-random在带有过期时间的键中随机选择。allkeys-random随机删除所有键,直到腾出足够内存为止。volatile-ttl根据键值对象的 ttl 属性,删除最近将要过期数据。如果没有,回退到 noeviction 策略。noeviction默认策略,不会删除任何数据,拒绝所有写入操作并返回客户端错误信息(error)OOMcommand not allowed when used memory,此时 Redis 只响应读操作。

如果没有符合前提条件的 key 被淘汰,那么 volatile-lru、volatile-random 、

volatile-ttl 相当于 noeviction(不做内存回收)。

动态修改淘汰策略:

redis> config set maxmemory-policy volatile-lru复制代码

建议使用 volatile-lru,在保证正常服务的情况下,优先删除最近最少使用的 key。

LRU 淘汰原理

思考:基于一个数据结构做缓存,怎么实现 LRU——最长时间不被访问的元素在超过容量时删除?

问题:如果基于传统 LRU 算法实现 Redis LRU 会有什么问题?

需要额外的数据结构存储,消耗内存。

Redis LRU 对传统的 LRU 算法进行了改良,通过随机采样来调整算法的精度。

如果淘汰策略是 LRU,则根据配置的采样值 maxmemory_samples(默认是 5 个),随机从数据库中选择 m 个 key, 淘汰其中热度最低的 key 对应的缓存数据。所以采样参数 m 配置的数值越大, 就越能精确的查找到待淘汰的缓存数据,但是也消耗更多的 CPU 计算,执行效率降低。

问题:如何找出热度最低的数据?

Redis 中所有对象结构都有一个 lru 字段, 且使用了 unsigned 的低 24 位,这个字段用来记录对象的热度。对象被创建时会记录 lru 值。在被访问的时候也会更新 lru 的值。但是不是获取系统当前的时间戳,而是设置为全局变量 server.lruclock 的值。

源码:server.h

typedef struct redisObject {    unsigned type:4;    unsigned encoding:4;    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or    *LFU data (least significant 8 bits frequency    *and most significant 16 bits access time). */    int refcount;    void *ptr;} robj;复制代码

server.lruclock 的值怎么来的?

Redis 中 有 个 定 时 处 理 的 函 数 serverCron , 默 人 每 100 好 秒 调 用 函 数 updateCachedTime 更新一次全局变量的 server.lruclock 的值,它记录的是当前 unix 时间戳。

源码:server.c

void updateCachedTime(void) {    time_t unixtime = time(NULL);    atomicSet(server.unixtime,unixtime);    server.mstime = mstime();    struct tm tm;    localtime_r(&server.unixtime,&tm);    server.daylight_active = tm.tm_isdst;}复制代码

问题:为什么不获取精确的时间而是放在全局变量中?不会有延迟的问题吗?

这样函数 lookupKey 中更新数据的 lru 热度值时,就不用每次调用系统函数 time,可以提高执行效率。

OK,当对象里面已经有了 LRU 字段的值,就可以评估对象的热度了。

函数 estimateObjectIdleTime 评估指定对象的 lru 热度,思想就是对象的 lru 值和全局的 server.lruclock 的差值越大(越久没有得到更新), 该对象热度越低。

源码 evict.c

/* Given an object returns the min number of milliseconds the object was never*requested, using an approximated LRU algorithm. */ unsigned long long estimateObjectIdleTime(robj *o) {    unsigned long long lruclock = LRU_CLOCK();    if (lruclock >= o->lru) {        return (lruclock - o->lru) * LRU_CLOCK_RESOLUTION;    } else {        return (lruclock + (LRU_CLOCK_MAX - o->lru)) *        LRU_CLOCK_RESOLUTION;    }}复制代码

server.lruclock 只有 24 位,按秒为单位来表示才能存储 194 天。当超过 24bit 能表示的最大时间的时候,它会从头开始计算。

server.h

#define LRU_CLOCK_MAX ((1<lru */复制代码

在这种情况下,可能会出现对象的 lru 大于 server.lruclock 的情况,如果这种情况出现那么就两个相加而不是相减来求最久的 key。

为什么不用常规的哈希表+双向链表的方式实现?需要额外的数据结构,消耗资源。

而 Redis LRU 算法在 sample 为 10 的情况下,已经能接近传统 LRU 算法了。

redis.io/topics/lru-…

问题:除了消耗资源之外,传统 LRU 还有什么问题?

如图,假设 A 在 10 秒内被访问了 5 次,而 B 在 10 秒内被访问了 3 次。因为 B 最后一次被访问的时间比 A 要晚,在同等的情况下,A 反而先被回收。

问题:要实现基于访问频率的淘汰机制,怎么做?

LFU

server.h

typedef struct redisObject {    unsigned type:4;    unsigned encoding:4;    unsigned lru:LRU_BITS;     /* LRU time (relative to global lru_clock) or    *LFU data (least significant 8 bits frequency    *and most significant 16 bits access time). */    int refcount;    void *ptr;} robj;复制代码

当这 24 bits 用作 LFU 时,其被分为两部分:

高16 位用来记录访问时间(单位为分钟,ldt,last decrement time)等 8 位用来记录访问频率,简称 counter(logc,logistic counter)

counter 是用基于概率的对数计数器实现的,8 位可以表示百万次的访问频率。

对象被读写的时候,lfu 的值会被更新。

db.c——lookupKey

void updateLFU(robj *val) {unsigned long counter = LFUDecrAndReturn(val);counter = LFULogIncr(counter);val->lru = (LFUGetTimeInMinutes()<<8) | counter;}复制代码

增长的速率由,lfu-log-factor 越大,counter 增长的越慢 redis.conf 配置文件

# lfu-log-factor 10复制代码

如果计数器只会递增不会递减,也不能体现对象的热度。没有被访问的时候,计数器怎么递减呢?

减少的值由衰减因子 lfu-decay-time(分钟)来控制,如果只是 1 的话,N 分钟没有访问就要减少 N。

redis.conf 配置文件

内存淘汰算法_「承」Redis 原理篇——Redis 的内存回收机制相关推荐

  1. 局域网arp攻击_「网络安全」常见攻击篇(23)——ARP攻击

    什么是ARP攻击? ARP攻击是利用ARP协议设计时缺乏安全验证漏洞来实现的,通过伪造ARP数据包来窃取合法用户的通信数据,造成影响网络传输速率和盗取用户隐私信息等严重危害. ARP攻击原理 ARP病 ...

  2. js实现kmp算法_「leetcode」459.重复的子字符串:KMP算法还能干这个!

    不瞒你说,重复子串问题,KMP很拿手 题目459.重复的子字符串 给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成.给定的字符串只含有小写英文字母,并且长度不超过10000. 示例 1: ...

  3. 【承】Redis 原理篇——Redis 高性能深入剖析

    前言 关于 Redis 的"起承转合",我前面已经用五个篇章的长度作了一个 Redis 基础篇--"起"篇的详细阐述,相信大家无论之前有没有接触过 Redis, ...

  4. java 最少使用(lru)置换算法_「面试」LRU了解么?看看LinkedHashMap如何实现LRU算法...

    以下内容均是本人原创,希望你看完之后能有更多更深入的了解,欢迎关注➕ 问题:使用Java完成一个简单的LRU算法 什么是LRU算法 LRU(Least Recently Used),也就是最近最少使用 ...

  5. admm算法_「优化」交替方向乘子法(ADMM)的基本原理

    编者按:本文介绍ADMM最基本情形的推导.通过这篇文章,你将了解ADMM算法的基本思路,收敛性分析的基本原理,和它理论上的一些局限性. 文章作者:覃含章 责任编辑:覃含章 本文的内容主要来自著名的讲义 ...

  6. java 平均分配算法_「角平分线」Java 计算角平分线 - seo实验室

    角平分线 有三个点,计算出角平分线.首先要算出两个点之间的方位角,根据方位角算出夹角.下面以角平分线长度是20示例计算. double dStartAngle = Math.atan2(mdE1 - ...

  7. 如何看待 2020 届校招算法岗「爆炸」的情况?英雄所见略同

    来自:计算机视觉联盟公众号 转载 :知乎问题 如何看待 2020 届校招算法岗「爆炸」的情况? 链接:https://www.zhihu.com/question/342267611 本文仅作为学术交 ...

  8. 如何看待 2020 届校招算法岗「爆炸」的情况?

    编辑:忆臻 https://www.zhihu.com/question/342267611 本文仅作为学术分享,如果侵权,会删文处理 如何看待 2020 届校招算法岗「爆炸」的情况? 作者:Ted ...

  9. 如果我问你:排序算法的「稳定性」有何意义?你怎么回答?

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"加群"加入公众号专属技术群 欢迎跳转到本文的原文链接:https://honeypps ...

最新文章

  1. 阿里云 apt-get mysql_阿里云Ubuntu 16.04 x64 装配Mysql 5.7
  2. 【数学建模】种群竞争模型(最优化)
  3. Django学习手册 - ORM数据类型
  4. Windows7 Credential Manage
  5. 各种平台的表达芯片跟mRNA-seq数据比较
  6. P5217 贫穷 平衡树
  7. office数据集dslr_DSLR的完整形式是什么?
  8. 计算机网络的资源共享功能包,计算机网络的资源共享功能包括
  9. P5725 【深基4.习8】求三角形(python3实现)
  10. linux mtu日志,linux MTU调整
  11. mfc110.dll丢失,解决方法
  12. 微信小程序下载文件到本地
  13. eclipse中文版 中英文切换
  14. 蓝桥杯官网 试题 基础练习 阶乘计算(C++ 高精度)
  15. 【推荐算法】协同过滤推荐算法综述 传统推荐算法综述
  16. 关于window10系统找不到Realtek高清晰音频管理器解决方法
  17. 腾讯云学生服务器搭建个人网站——配置web开发环境详细步骤
  18. 【24计算机考研】备考前必须了解的避坑小知识,建议收藏
  19. 任正非:管理上的灰色,是我们的生命之树
  20. 激光清洗的优点和实际案例

热门文章

  1. 一学即懂得计算机视觉
  2. php要每次循环两个数据库,从前台接收的json数据 2个数组,php同时插入sql数据库,循环要怎么套呢?...
  3. java通过ftp方式上传_通过FTP以Java方式上传文件
  4. 用PHP代码实现简单的工厂模式,用PHP代码实现简单的工厂模式
  5. linux下安装mysql5.7.25详细教程
  6. Vue的组件为什么要export default
  7. Linux USB 驱动开发(五)—— USB驱动程序开发过程简单总结
  8. Android FFmpeg移植总攻略——获取视频帧数(亲测可用)
  9. handler回调主线程_Android使用Handler实现子线程与子线程、子线程与主线程之间通信...
  10. 量子计算机模拟其他系统,一种量子计算机的模拟控制方法、系统及相关组件技术方案...