Redis-内存模型

参考资料: 极客时间 Redis 亚风

内存管理

从两个问题入手:
1 Redis是如何知道⼀个key是否过期呢?
2 是不是TTL到期就⽴即删除了呢?
Redis是K-V内存数据库,所有的K、V都保存在Dict中。不过在其db结构体中有5个Dict,我们只用关注两个Dict 和 Expires:⼀个⽤来记录K-V,另⼀个⽤来记录K-TTL。

// 6.2的源码
typedef struct redisDb {dict *dict;                 /*  数据存储dict, ALLKEYS*/dict *expires;              /* 上面的keys如果设置了过期时间则会存储到这里*/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 */unsigned long expires_cursor; /* Cursor of the active expire cycle. */list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;

在这里插入图片描述

在这里插入图片描述
怎么真正的删除Key呢?
1 惰性删除
访问key的时候,检查该key的存活时间,如果过期才执⾏删除。也就是用户发起一个查询的时候来检查如果过期就删除。

robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {expireIfNeeded(db,key); // 过期删除KEYreturn lookupKey(db,key,flags);
}

2 周期删除
顾明思议是通过⼀个定时任务,周期性的抽样部分过期的key,然后执⾏删除。执⾏周期有两种:
• Redis会设置⼀个定时任务serverCron(),按照server.hz的频率来执⾏过期key清理,模式为SLOW
• Redis的每个事件循环前会调⽤beforesleep()函数,执⾏过期key清理,模式为FAST
initServer 是时候会创建一个serverCron定时任务

/* Create the timer callback, this is our way to process many background* operations incrementally, like clients timeout, eviction of unaccessed* expired keys and so forth. */if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {serverPanic("Can't create event loop timers.");exit(1);}
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {// 更新lruclock 到当前时间 为后期的LRU和LFU做准备unsigned int lruclock = getLRUClock();/* for debug purposes: skip actual cron work if pause_cron is on */if (server.pause_cron) return 1000/server.hz;/* We need to do a few operations on clients asynchronously. */clientsCron();/* 数据清理. */databasesCron();return 1000/server.hz; // 返回时间间隔 执行是时候和上一次返回值做比较
}
// databasesCron 的逻辑
void databasesCron(void) {/* Expire keys by random sampling. Not required for slaves* as master will synthesize DELs for us. */if (server.active_expire_enabled) {if (iAmMaster()) {// 采用的是 Slow模式activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);} else {expireSlaveKeys();}}
}
// beforesleep 里面的逻辑if (server.active_expire_enabled && server.masterhost == NULL)activeExpireCycle(ACTIVE_EXPIRE_CYCLE_FAST); // Fast模式

SLOW模式规则:
1 执⾏频率受server.hz影响,默认为10,即每秒执⾏10次,每个执⾏周期100ms。
2 执⾏清理耗时不超过⼀次执⾏周期的25%。
3 逐个遍历db,逐个遍历db中的bucket,抽取20个key判断是否过期
4 如果没达到时间上限(25ms)且过期key⽐例⼤于10%,再进⾏⼀次抽样,否则结束。
FAST模式规则(过期key⽐例⼩于10%不执⾏):
1 执⾏频率受beforeSleep()调⽤频率影响,但两次FAST模式间隔不低于2ms
2 执⾏清理耗时不超过1ms
3 逐个遍历db,逐个遍历db中的bucket,抽取20个key判断是否过期
4 如果没达到时间上限(1ms)并且过期key⽐例⼤于10%,再进⾏⼀次抽样,否则结束
总结
RedisKey的TTL记录⽅式:在RedisDB中通过⼀个Dict记录每个Key的TTL时间过期key的删除策略:
• 惰性清理:每次查找key时判断是否过期,如果过期则删除
• 定期清理:定期抽样部分key,判断是否过期,如果过期则删除。
定期清理的2种模式:
• SLOW模式执⾏频率默认为10,每次不超过25ms
• FAST模式执⾏频率不固定,但两次间隔不低于2ms,每次耗时不超过1ms
内存淘汰:就是当Redis内存使⽤达到设置的阈值时,Redis主动挑选部分key删除以释放更多内存的流程。
内存淘汰入口
processCommand

if (server.maxmemory && !server.lua_timedout) {int out_of_memory = (performEvictions() == EVICT_FAIL);/* performEvictions may evict keys, so we need flush pending tracking* invalidation keys. If we don't do this, we may get an invalidation* message after we perform operation on the key, where in fact this* message belongs to the old value of the key before it gets evicted.*/trackingHandlePendingKeyInvalidations();/* performEvictions may flush slave output buffers. This may result* in a slave, that may be the active client, to be freed. */if (server.current_client == NULL) return C_ERR;int reject_cmd_on_oom = is_denyoom_command;/* If client is in MULTI/EXEC context, queuing may consume an unlimited* amount of memory, so we want to stop that.* However, we never want to reject DISCARD, or even EXEC (unless it* contains denied commands, in which case is_denyoom_command is already* set. */if (c->flags & CLIENT_MULTI &&c->cmd->proc != execCommand &&c->cmd->proc != discardCommand &&c->cmd->proc != resetCommand) {reject_cmd_on_oom = 1;}if (out_of_memory && reject_cmd_on_oom) {rejectCommand(c, shared.oomerr);return C_OK;}

Redis⽀持8种不同策略来选择要删除的key:
noeviction:不淘汰任何key,但是内存满时不允许写⼊新数据,默认就是这种策略。
volatile-ttl:对设置了TTL的key,⽐较key的剩余TTL值,TTL越⼩越先被淘汰
allkeys-random:对全体key,随机进⾏淘汰。也就是直接从dict中随机挑选
volatile-random:对设置了TTL的key,随机进⾏淘汰。也就是从expires中随机挑选。
allkeys-lru:对全体key,基于LRU算法进⾏淘汰
volatile-lru:对设置了TTL的key,基于LRU算法进⾏淘汰
allkeys-lfu: 对全体key,基于LFU算法进⾏淘汰
volatile-lfu:对设置了TTL的key,基于LFU算法进⾏淘汰
⽐较容易混淆的有两个:
LRU (Least Recently Used),最少最近使⽤。⽤当前时间减去最后⼀次访问时间,这个值越⼤则淘汰优先级越⾼。
LFU (Least Frequently used),最少频率使⽤。会统计每个key的访问频率,值越⼩淘汰优先级越⾼。

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). */// LRU 以毫秒为单位记录最近一次访问时间,长度24bit// LFU 高16位以分钟为单位记录最近一次访问时间,低8位记录逻辑访问次数int refcount; // 引用计数void *ptr;  // 数据指针 指向真实数据
} robj;

LFU的访问次数之所以叫做逻辑访问次数,是因为并不是每次key被访问都计数,⽽是通过运算:
1 ⽣成0~1之间的随机数R。
2 计算1/(旧次数 x Ifu_log_factor +1),记录为P,lfu_log_factor默认为10。
3 如果R < P,则计数器 +1,且最⼤不超过255。
4 访问次数会随时间衰减,距离上⼀次访问时间每隔 Ifu_decay_time 分钟(默认1),计数器 -1。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/285627.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Neural Network——神经网络

1.feature reusing——特征复用 1.1 什么是特征复用 回顾我们之前所学习的模型&#xff0c;本质上都是基于线性回归&#xff0c;但却都可以运用于非线性相关的数据&#xff0c;包括使用了如下方法 增加更多的特征产生新的特征&#xff08;多项式回归&#xff09;核函数 在本身…

C#深拷贝效率对比

对于浅拷贝和深拷贝&#xff0c;前面的文章已经说明了。 C#浅拷贝和深拷贝数据-CSDN博客 本篇说一下&#xff0c;深拷贝的效率问题&#xff0c;效率一直是程序追求的&#xff0c;效率越高肯定越好&#xff0c;有时候功能是实现了&#xff0c;但是运行以及处理数据的效率非常低…

Ubuntu 18.04配置NFS服务器以及配置时遇到NFS问题

1.安装相关软件 sudo apt-get install nfs-kernel-server sudo apt-get install nfs-common 2.配置共享目录 2.1修改exports文件 sudo vi /etc/exports在最后添加如下并保存退出 /home/xiaowu/nfs 192.168.31*(rw,sync,no_root_squash,no_subtree_check) /home/xiaowu/nfs…

【密码学】群的证明(习题)

0.前置知识 1.习题 记录一次密码学作业~群的判定 2.求解

〖大前端 - 基础入门三大核心之JS篇(57)〗- 继承

说明&#xff1a;该文属于 大前端全栈架构白宝书专栏&#xff0c;目前阶段免费&#xff0c;如需要项目实战或者是体系化资源&#xff0c;文末名片加V&#xff01;作者&#xff1a;哈哥撩编程&#xff0c;十余年工作经验, 从事过全栈研发、产品经理等工作&#xff0c;目前在公司…

骨传导耳机和气传导耳机有什么区别?谁更值得入手?

先说答案&#xff0c;骨传导耳机和气传导耳机的佩戴方式和传声方式不同&#xff0c;并且骨传导耳机相比于气传导耳机更值得入手。 一、骨传导耳机和气传导耳机有什么区别 1、佩戴方式不同 骨传导耳机采用一体式耳挂佩戴或耳夹式佩戴&#xff0c;气传导耳机采用分体式耳挂设计…

采样率8kHz~96kHz单端输入ADC芯片CJC1808

音频模/数转换器是一种用于将模拟音频信号转换成数字信号的设备&#xff0c;可以提供高质量的音频转换功能&#xff1b;被广泛应用于录音室、数字音频处理、混音、模拟音频处理等应用中&#xff1b;是当今数字音频处理中不可或缺的一部分。 ADC芯片可以提供宽频带的模拟输入&a…

SpringCloud微服务 【实用篇】| Docker镜像、容器、数据卷操作

目录 一&#xff1a;Docker基本操作 1. 镜像操作 镜像相关命令 2. 容器操作 容器相关命令 3. 数据卷&#xff08;容器数据管理&#xff09; 数据卷 操作数据卷 挂载数据卷 挂载的方式区别 前些天突然发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0…

如何利用CHAT写C程序?

问CHAT&#xff1a;用c语言编写在二维字符数组中查找某个字符串 CHAT回复&#xff1a;以下是一个简单的C程序&#xff0c;它将在二维字符数组中查找特定的字符串。 c #include <stdio.h> #include <string.h> void search(char arr[100][100], int r, char* str) …

Java泛型(1)

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 本…

xxl-job 分布式调度学习笔记

1.概述 1.1什么是任务调度 业务场景&#xff1a; 上午10点&#xff0c;下午2点发放一批优惠券 银行系统需要在信用卡到期还款日的前三天进行短信提醒 财务系统需要在每天凌晨0:10分结算前一天的财务数据&#xff0c;统计汇总 不同系统间的数据需要保持一致&#xff0c;这时…

构建健康中国:医保支付购药系统的技术实现

在数字化时代&#xff0c;医保支付购药系统的技术实现成为医疗保障体系不可或缺的一环。通过整合医疗资源、优化服务流程&#xff0c;这一系统为患者提供了更便捷、高效的医疗服务。本文将深入探讨医保支付购药系统的技术架构与实现方法。 1. 技术架构概述 医保支付购药系统…