Redis 的键管理

news2025/2/4 21:59:05

一、Redis 数据库管理

Redis 是一个键值对(key-value pair)的数据库服务器,其数据保存在 src/server.h/redisDb 中(网上很多帖子说在 redis.h 文件中,但是 redis 6.x版本目录中都没有这个文件。redisDb 结构应该在 server.h文件中)

typedef redisServer {
    ....
    
    // Redis数据库
    redisDb *db;
    
    ....
}

Redis 默认会创建 16 个数据库,每个数据库是独立互不影响。其默认的目标数据库是 0 号数据库,可以通过 select 命令来切换目标数据库。在 redisClient 结构中记录客户端当前的目标数据库:

typedef struct redisClient {

    // 套接字描述符
    int fd;

    // 当前正在使用的数据库
    redisDb *db;

    // 当前正在使用的数据库的 id (号码)
    int dictid;

    // 客户端的名字
    robj *name;             /* As set by CLIENT SETNAME */

} redisClient;

下面是客户端和服务器状态之间的关系实例,客户端的目标数据库目前为 1 号数据库:

通过修改 redisClient.db 的指针来指向不同数据库,这也就是 select 命令的实现原理。但是,到目前为止,Redis 仍然没有可以返回客户端目标数据库的命令。虽然在 redis-cli 客户端中输入时会显示:

redis> SELECT 1
Ok
redis[1]> 

但是在其他语言客户端没有显示目标数据库的号端,所以在频繁切换数据库后,会导致忘记目前使用的是哪一个数据库,也容易产生误操作。因此要谨慎处理多数据库程序,必须要执行时,可以先显示切换指定数据库,然后再执行别的命令。

二、Redis 数据库键

2.1 数据库键空间

Redis 服务器中的每一个数据库是由一个 server.h/redisDb 结构来表示的,其具体结构如下:

typedef struct redisDb {
    //数据库键空间
    dict *dict;                 /* The keyspace for this DB */
    //键的过期时间,字典的值为过期事件 UNIX 时间戳
    dict *expires;              /* Timeout of keys with a timeout set */
    //正处于阻塞状态的键
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)*/
    //可以解除阻塞的键
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    //正在被 WATCH 命令监视的键
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    //数据库号端
    int id;                     /* Database ID */
    //数据库键的平均 TTL,统计信息
    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 就是数据库的 key, 每个 key 都是一个字符串对象
  • 键空间的 value 是数据库的 value, 每个 value 可以是字符串对象、列表对象和集合对象等等任意一种 Redis 对象

举个实例,若在空白数据库中执行一下命令:插入字符串对象、列表对象和哈希对象

# 插入一个字符串对象
redis> SET message "hello world"
OK
# 插入包含三个元素的列表对象
redis> RPUSH alphabet "a" "b" "c"
(integer)3
# 插入包含三个元素的哈希表对象
redis> HSET book name "Redis in Action"
(integer) 1
redis> HSET book author "Josiah L. Carlson"
(integer) 1
redis> HSET book publisher "Manning"
(integer) 1

所以说 redis 对数据的增删改查是通过操作 dict 来操作 Redis 中的数据

2.2 数据库键的过期

我们可以通过两种方式设置键的生命周期:

  • 通过 EXPIRE 或者 PEXPIRE 命令来为数据库中的某个键设置生存时间(TTL,Time To Live)。在经过 TTL 个生存时间后,服务器会自动删除生存时间为0 的键。比如:

    redis> set key value
    OK
    # 设置键的 TTL 为 5
    redis> EXPIRE key 5
    (integer)1
    
  • 此外,客户端也可以通过 EXPIREAT 或者PEXPIREAT 命令,为数据库中的某个键设置过期时间(expire time)。过期时间是一个 UNIX 时间戳,当过期时间来临时,服务器就会自动从数据库中删除这个键。比如

    redis> SET key value
    OK
    redis> EXPIREAT key 1377257300
    (integer) 1
    # 当前系统时间
    redis> TIME
    1)"1377257296"
    # 过一段时间后,再查询key 
    redis> GET key // 1377257300
    (nil)
    
2.2.1 过期时间

redisDb 中的dict *dictdict *expires 字典 分别保存了数据库中的键和键的过期时间,分别叫做键空间过期字典

  • 过期字典的键是一个指向键空间中的某个键对象
  • 过期字典的值是一个 long long 类型的整数,这个整数保存了键所指向的数据库键的过期时间

2.3 过期键的删除策略

对于已经过期的数据是如何删除这些过期键的呢?主要有两种方式:惰性删除和定期删除:

1.惰性删除

是指 Redis 服务器不主动删除过期的键值,而是通过访问键值时,检查当前的键值是否过期

  • 如果过期则执行删除并返回 null
  • 没有过期则正常访问值信息给客户端

惰性删除的源码在 src/db.c/expireIfNeeded 方法中

int expireIfNeeded(redisDb *db, robj *key) {

    // 判断键是否过期

    if (!keyIsExpired(db,key)) return 0;

    if (server.masterhost != NULL) return 1;

    /* 删除过期键 */

    // 增加过期键个数

    server.stat_expiredkeys++;

    // 传播键过期的消息

    propagateExpire(db,key,server.lazyfree_lazy_expire);

    notifyKeyspaceEvent(NOTIFY_EXPIRED,

        "expired",key,db->id);

    // server.lazyfree_lazy_expire 为 1 表示异步删除,否则则为同步删除

    return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :

                                         dbSyncDelete(db,key);

}

// 判断键是否过期

int keyIsExpired(redisDb *db, robj *key) {

    mstime_t when = getExpire(db,key);

    if (when < 0) return 0; 

    if (server.loading) return 0;

    mstime_t now = server.lua_caller ? server.lua_time_start : mstime();

    return now > when;

}

// 获取键的过期时间

long long getExpire(redisDb *db, robj *key) {

    dictEntry *de;

    if (dictSize(db->expires) == 0 ||

       (de = dictFind(db->expires,key->ptr)) == NULL) return -1;

    serverAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);

    return dictGetSignedIntegerVal(de);

}
2.定期删除

与惰性删除不同,定期删除是指 Redis 服务器会每隔一段时间就会检查一下数据库,看看是否有过期键可以清除,默认情况下,Redis 定期检查的频率是每秒扫描 10 次,这个值在 redis.conf 中的 “hz” , 默认是 10 ,可以进行修改。

定期删除的扫描并不是遍历所有的键值对,这样的话比较费时且太消耗系统资源。Redis 服务器采用的是随机抽取形式,每次从过期字典中,取出 20 个键进行过期检测,过期字典中存储的是所有设置了过期时间的键值对。如果这批随机检查的数据中有 25% 的比例过期,那么会再抽取 20 个随机键值进行检测和删除,并且会循环执行这个流程,直到抽取的这批数据中过期键值小于 25%,此次检测才算完成。

定期删除的源码在 expire.c/activeExpireCycle 方法中:

void activeExpireCycle(int type) {

    static unsigned int current_db = 0; /* 上次定期删除遍历到的数据库ID */

    static int timelimit_exit = 0;      

    static long long last_fast_cycle = 0; /* 上次执行定期删除的时间点 */

    int j, iteration = 0;

    int dbs_per_call = CRON_DBS_PER_CALL; // 需要遍历数据库的数量

    long long start = ustime(), timelimit, elapsed;

    if (clientsArePaused()) return;

    if (type == ACTIVE_EXPIRE_CYCLE_FAST) {

        if (!timelimit_exit) return;

        // ACTIVE_EXPIRE_CYCLE_FAST_DURATION 快速定期删除的执行时长

        if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return;

        last_fast_cycle = start;

    }

    if (dbs_per_call > server.dbnum || timelimit_exit)

        dbs_per_call = server.dbnum;

    // 慢速定期删除的执行时长

    timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;

    timelimit_exit = 0;

    if (timelimit <= 0) timelimit = 1;

    if (type == ACTIVE_EXPIRE_CYCLE_FAST)

        timelimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /* 删除操作花费的时间 */

    long total_sampled = 0;

    long total_expired = 0;

    for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {

        int expired;

        redisDb *db = server.db+(current_db % server.dbnum);

        current_db++;

        do {

            // .......

            expired = 0;

            ttl_sum = 0;

            ttl_samples = 0;

            // 每个数据库中检查的键的数量

            if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)

                num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;

            // 从数据库中随机选取 num 个键进行检查

            while (num--) {

                dictEntry *de;

                long long ttl;

                if ((de = dictGetRandomKey(db->expires)) == NULL) break;

                ttl = dictGetSignedInteger

                // 过期检查,并对过期键进行删除

                if (activeExpireCycleTryExpire(db,de,now)) expired++;

                if (ttl > 0) {

                    ttl_sum += ttl;

                    ttl_samples++;

                }

                total_sampled++;

            }

            total_expired += expired;

            if (ttl_samples) {

                long long avg_ttl = ttl_sum/ttl_samples;

                if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;

                db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);

            }

            if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */

                elapsed = ustime()-start;

                if (elapsed > timelimit) {

                    timelimit_exit = 1;

                    server.stat_expired_time_cap_reached_count++;

                    break;

                }

            }

            /* 判断过期键删除数量是否超过 25% */

        } while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);

    }

    // .......

}

以上就是Redis 的删除策略。下面来看一个面试题:

面试题:你知道 Redis 内存淘汰策略和键的删除策略的区别吗?

Redis 内存淘汰策略

我们可以通过 config get maxmemory-policy 命令来查看当前 Redis 的内存淘汰策略:

127.0.0.1:6379> config get maxmemory-policy

1) "maxmemory-policy"

2) "noeviction"

当前服务器设置的是 noeviction 类型的,对于 redis 6.x版本,主要有以下几种内存淘汰策略

  • noeviction:不淘汰任何数据,当内存不足时,执行缓存新增操作会报错,它是 Redis 默认内存淘汰策略。
  • allkeys-lru:淘汰整个键值中最久未使用的键值。
  • allkeys-random:随机淘汰任意键值。
  • volatile-lru:淘汰所有设置了过期时间的键值中最久未使用的键值。
  • volatile-random:随机淘汰设置了过期时间的任意键值。
  • volatile-ttl:优先淘汰更早过期的键值。
  • volatile-lfu: 淘汰所有设置了过期时间的键值中最少使用的键值。
  • alkeys-lfu: 淘汰整个键值中最少使用的键值

也就是 alkeys 开头的表示从所有键值中淘汰相关数据,而 volatile 表示从设置了过期键的键值中淘汰数据。

Redis 内存淘汰算法

内存淘汰算法主要分为 LRU 和 LFU 淘汰算法

LRU(Least Recently Used) 淘汰算法

是一种常用的页面置换算法,LRU 是基于链表结构实现,链表中的元素按照操作顺序从前往后排列。最新操作的键会被移动到表头,当需要进行内存淘汰时,只需要删除链表尾部的元素。

Redis 使用的是一种近似 LRU 算法,目的是为了更好的节约内存,给现有的数据结构添加一个额外的字段,用于记录此键值的最后一次访问时间。Redis 内存淘汰时,会使用随机采样的方式来淘汰数据,随机取5个值,然后淘汰最久没有使用的数据。

LFU(Least Frequently Used)淘汰算法

根据总访问次数来淘汰数据,核心思想是如果数据过去被访问多次,那么将来被访问的频率也更高

参考资料

《Redis 设计与实现》
https://kaiwu.lagou.com/course/courseInfo.htm?courseId=59#/detail/pc?id=1779

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mysql SQL执行超时问题

show variables like max_execution_time 使用这个命令查看了&#xff0c;没有设置sql执行超时时间&#xff0c;那么大概率问题就出在阿里的Druid数据库连接池出了问题 尝试着socketTimeout由60000毫秒改成10000毫秒&#xff0c;果然执行了十几秒就超时报错了 socketTime…

【uniapp】uniapp中本地存储sqlite数据库保姆级使用教程(附完整代码和注释)

数据库请求接口封装 uniapp中提供了plus.sqlite接口&#xff0c;在这里我们对常用的数据库请求操作进行了二次封装 这里的dbName、dbPath、recordsTable 可以根据你的需求自己命名 module.exports {/** * type {String} 数据库名称*/dbName: salary,/*** 数据库地址* type {…

LVM系统逻辑卷

1.lvm的来源 我们在工作中经常遇到服务器存储数据的分区磁盘空间不够的情况&#xff0c;尤其是当我们的业务是视频的时候&#xff0c;大批量用户上传和下载视频&#xff0c;磁盘空间需要不停的调整。如果我们作为运维每天的工作就是加硬盘是不是有点扯&#xff0c;而且换硬盘的…

signaltap立即触发的错误解决方法

signaltap点下run analysis后没有等到触发条件满足就触发了&#xff0c;原因是触发方式设置错误&#xff0c;应修改触发方式&#xff1a; 将Trigger flow control 从State-based 改为Sequential。

Java学习系列(七)

1.Java多态 多态是同一个行为具有多个不同表现形式或形态的能力&#xff0c; 多态就是同一个接口&#xff0c;使用不同的实例而执行不同操作。 多态的优点 1. 消除类型之间的耦合关系2. 可替换性3. 可扩充性4. 接口性5. 灵活性6. 简化性 多态存在的三个必要条件 继承重写…

鸿蒙原生应用/元服务开发-Stage模型能力接口(九)下

ohos.app.ability.UIAbility (UIAbility)Caller 通用组件Caller通信客户端调用接口, 用来向通用组件服务端发送约定数据。 Caller.call call(method: string, data: rpc.Parcelable): Promise<void>; 向通用组件服务端发送约定序列化数据。 系统能力&#xff1a;Syste…

XML简介 (EXtensible Markup Language)

XML简介 (EXtensible Markup Language) 可扩展标记语言 特点 XML与操作系统、编程语言的开发平台无关实现不同系统之间的数据交换 作用 数据交互配置应用程序和网站Ajax基石 XML标签 XML文档内容由一系列标签元素组成 <元素名 属性名"属性值">元素内容&l…

关于时区处理策略

前端会通过 App-Id 请求头附带 客户端时区 信息 前端传入的如果是 字符串&#xff0c;会自动根据 请求的客户端时区 解析为对应的 日期 如果前端传入的是时间戳&#xff0c;则无需额外解析转换 如果是 商户后台、管理后台 都统一基于 商户所在国家的时区&#xff08;总台目前…

<九>JavaScript中函数,函数的定义方式,形参、实参和返回值

JavaScript中的函数&#xff0c;也是一个对象&#xff0c;函数对象有所有普通对象有的性质。还可以在这个对象中封装一些功能&#xff0c;保存一些代码&#xff0c;在需要的时候调用这些代码&#xff0c;用typeof查看时&#xff0c;会返回类型&#xff1a;function。 定义函数…

线程学习(2)

&#x1f495;"i need your breath"&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;线程学习(2) 前情回顾&#xff1a; 在上一篇博客中介绍到了进程与线程的区别&#xff0c;以及初步了解如何在Java实现多线程编程&#xff0c;通过内置的Thread类来…

eNSP错误40,原因三:windows10自带虚拟化软件Hyper-V

问题描述 Hyper-V软件与VirtualBox不兼容&#xff0c;一旦开启Hyper-V的话eNSP的路由器就会无法开启&#xff0c;显示ERROR 40 原理 大家注意看hypervisor的两种类型&#xff1a; 左边的是开启hypervisor的Type-1&#xff0c;hypervisor在启用的时候&#xff0c;宿主机也相…

SQL指南:掌握日期函数来查询和管理数据

文章目录 1. 引言2. 建立数据库表2.1 建表语句2.2 数据插入 查询案例3.1 查询当前日期的订单3.2 查询过去一周内的订单3.3 查询明天的日期3.4 查询今年的订单3.5 查询特定月份的订单 总结 1. 引言 在数据库管理中&#xff0c;处理日期和时间是一项基本但重要的任务。本指南将通…

Android笔记(十九):JetPack DataStore 之 Preferences DataStore

Jetpack DataStore 是一种数据存储解决方案&#xff0c;主要适用于小型数据的处理。它可以通过协议缓冲区存储键值对或类型化对象。DataStore 使用 Kotlin 协程和 Flow 以异步、一致的事务方式存储数据。DataStore有两种实现方式&#xff08;1&#xff09;Preferences DataStor…

如何搭建Web自动化测试框架?

在程序员的世界中&#xff0c;一切重复性的工作&#xff0c;都应该通过程序自动执行。「自动化测试」就是一个最好的例子。 随着互联网应用开发周期越来越短&#xff0c;迭代速度越来越快&#xff0c;只会点点点&#xff0c;不懂开发的手工测试&#xff0c;已经无法满足如今的…

Enge问题解决教程

目录 解决问题的一般步骤&#xff1a; 针对"Enge问题"的具体建议&#xff1a; 以下是一些普遍适用的解决问题的方法&#xff1a; 以下是一些更深入的Enge浏览器问题和解决办法&#xff1a; 浏览器性能问题&#xff1a; 浏览器插件与网站冲突&#xff1a; 浏览…

R语言中使用ggplot2绘制散点图箱线图,附加显著性检验

散点图可以直观反映数据的分布&#xff0c;箱线图可以展示均值等关键统计量&#xff0c;二者结合能够清晰呈现数据蕴含的信息。 本篇笔记主要内容&#xff1a;介绍R语言中绘制箱线图和散点图的方法&#xff0c;以及二者结合展示教程&#xff0c;添加差异比较显著性分析&#xf…

Android13 Wifi启动流程分析

Android13 Wifi启动流程分析 文章目录 Android13 Wifi启动流程分析一、正常开关wifi 启动流程1、WifiManager2、WifiServiceImpl3、ActiveModeWarden4、ConcreteClientModeManager5、WifiNative6、WifiVendorHal7、HalDeviceManager8、wifi.cpp 二、重启设备时自动开启wifi流程…

云原生之深入解析基于FunctionGraph在Serverless领域的FinOps的探索和实践

一、背景 Serverless 精确到毫秒级的按用付费模式使得用户不再需要为资源的空闲时间付费。然而&#xff0c;对于给定的某个应用函数&#xff0c;由于影响其计费成本的因素并不唯一&#xff0c;使得用户对函数运行期间的总计费进行精确的事先估计变成了一项困难的工作。以传统云…

亚马逊推出 Graviton4:具有 536.7 GBps 内存带宽的 96 核 ARM CPU

如今&#xff0c;许多云服务提供商都设计自己的芯片&#xff0c;但亚马逊网络服务 (AWS) 开始领先于竞争对手&#xff0c;目前其子公司 Annapurna Labs 开发的处理器可以与 AMD 和英特尔的处理器竞争。本周&#xff0c;AWS 推出了 Graviton4 SoC&#xff0c;这是一款基于 ARM 的…

斩获冠亚军!开源检索引擎Puck闪耀NeurIPS‘2023

近日&#xff0c;备受全球瞩目的国际向量检索大赛 Big-ANN in NeurIPS’23 的排行榜正式公布。百度搜索内容技术团队&#xff0c;以其自研 ANN 检索算法 PUCK 的出色性能表现&#xff0c;成功斩获Streaming Search赛道冠军与Filtered Search赛道亚军。 NeurIPS&#xff08;Con…