Redis设计与实现之字符串哈希表列表

news2024/11/29 14:54:40

目录

一、字符串

1、字符串编码

2、编码的选择

二、哈希表

1、字典编码的哈希表

2、压缩列表编码的哈希表

3、编码的选择

4、哈希命令的实现

三、列表

1、 编码的选择

2、 列表命令的实现

3、阻塞的条件

4、 阻塞

5、 阻塞因 LPUSH 、RPUSH 、LINSERT 等添加命令而被取消

6、 先阻塞先服务(FBFS)策略

7、 阻塞因超过最大等待时间而被取消


一、字符串

REDIS_STRING (字符串)是 Redis 使用得最为广泛的数据类型,它除了是 SET 、GET 等命令 的操作对象之外,数据库中的所有键,以及执行命令时提供给 Redis 的参数,都是用这种类型 保存的。

1、字符串编码

字符串类型分别使用 REDIS_ENCODING_INT 和 REDIS_ENCODING_RAW 两种编码:

  • REDIS_ENCODING_INT 使用 long 类型来保存 long 类型值。

  • REDIS_ENCODING_RAW 则使用 sdshdr 结构来保存 sds (也即是 char* )、long long 、 double 和 long double 类型值。

    换句话来说,在 Redis 中,只有能表示为 long 类型的值,才会以整数的形式保存,其他类型 的整数、小数和字符串,都是用 sdshdr 结构来保存。

2、编码的选择

新创建的字符串默认使用 REDIS_ENCODING_RAW 编码,在将字符串作为键或者值保存进数据库时,程序会尝试将字符串转为 REDIS_ENCODING_INT 编码。 3.2.3 字符串命令的实现Redis 的字符串类型命令,基本上是通过包装 sds 数据结构的操作函数来实现的,没有什么需 要说明的地方。

二、哈希表

REDIS_HASH (哈希表) 是 HSET 、 HLEN 等命令的操作对象,它使用REDIS_ENCODING_ZIPLIST 和 REDIS_ENCODING_HT 两种编码方式:

1、字典编码的哈希表

当哈希表使用字典编码时,程序将哈希表的键(key)保存为字典的键,将哈希表的值(value)保存为字典的值。

哈希表所使用的字典的键和值都是字符串对象。

下图展示了一个包含三个键值对的哈希表:

 

2、压缩列表编码的哈希表

当使用 REDIS_ENCODING_ZIPLIST 编码哈希表时,程序通过将键和值一同推入压缩列表,从而 形成保存哈希表所需的键 -值对结构: 

新添加的 key-value 对会被添加到压缩列表的表尾。 当进行查找/删除或更新操作时,程序先定位到键的位置,然后再通过对键的位置来定位值的位置。

3、编码的选择

创建空白哈希表时,程序默认使用 REDIS_ENCODING_ZIPLIST 编码,当以下任何一个条件被满

足时,程序将编码从切换为 REDIS_ENCODING_HT :
• 哈希表中某个键或某个值的长度大于 server.hash_max_ziplist_value (默认值为 64)。
• 压缩列表中的节点数量大于server.hash_max_ziplist_entries(默认值为512)。

4、哈希命令的实现

哈希类型命令的实现全都是对字典和压缩列表操作函数的包装,以及几个在两种编码之间进行转换的函数,没有特别要讲解的地方。

三、列表

REDIS_LIST (列表) 是 LPUSH 、 LRANGE 等命令的操作对象,它使用

REDIS_ENCODING_ZIPLIST 和 REDIS_ENCODING_LINKEDLIST 这两种方式编码:

1、 编码的选择

创建新列表时 Redis 默认使用 REDIS_ENCODING_ZIPLIST 编码,当以下任意一个条件被满足时,列表会被转换成 REDIS_ENCODING_LINKEDLIST 编码:
• 试 图 往 列 表 新 添 加 一 个 字 符 串 值, 且 这 个 字 符 串 的 长 度 超 过server.list_max_ziplist_value (默认值为 64 )。
• ziplist 包含的节点超过 server.list_max_ziplist_entries (默认值为 512 )。

2、 列表命令的实现

因为两种底层实现的抽象方式和列表的抽象方式非常接近,所以列表命令几乎就是通过一对一地映射到底层数据结构的操作来实现的。 既然这些映射都非常直观,这里就不做赘述了,在以下的内容中,我们将焦点放在 BLPOP 、BRPOP 和 BRPOPLPUSH 这个几个阻塞命令的实现原理上。

3、阻塞的条件

BLPOP 、BRPOP 和 BRPOPLPUSH 三个命令都可能造成客户端被阻塞,以下将这些命令统 称为列表的阻塞原语。

阻塞原语并不是一定会造成客户端阻塞:

• 只有当这些命令被用于空列表时,它们才会阻塞客户端。

• 如果被处理的列表不为空的话,它们就执行无阻塞版本的 LPOP 、RPOP 或 RPOPL- PUSH 命令。

作为例子,以下流程图展示了 BLPOP 决定是否对客户端进行阻塞过程:

4、 阻塞

 当一个阻塞原语的处理目标为空键时,执行该阻塞原语的客户端就会被阻塞。 阻塞一个客户端需要执行以下步骤:

1. 将客户端的状态设为“正在阻塞”,并记录阻塞这个客户端的各个键,以及阻塞的最长时(timeout)等数据。

2. 将客户端的信息记录到server.db[i]->blocking_keys中(其中i为客户端所使用的数 据库号码)。

3. 继续维持客户端和服务器之间的网络连接,但不再向客户端传送任何信息,造成客户端 阻塞。

步骤 2 是将来解除阻塞的关键,server.db[i]->blocking_keys 是一个字典,字典的键是那 些造成客户端阻塞的键,而字典的值是一个链表,链表里保存了所有因为这个键而被阻塞的客 户端(被同一个键所阻塞的客户端可能不止一个):

在上图展示的 blocking_keys 例子中,client2 、client5 和 client1 三个客户端就正被 key1 阻塞,而其他几个客户端也正在被别的两个 key 阻塞。

当客户端被阻塞之后,脱离阻塞状态有以下三种方法:
1. 被动脱离:有其他客户端为造成阻塞的键推入了新元素。
2. 主动脱离:到达执行阻塞原语时设定的最大阻塞时间。
3. 强制脱离:客户端强制终止和服务器的连接,或者服务器停机。

以下内容将分别介绍被动脱离和主动脱离的实现方式。

5、 阻塞因 LPUSH 、RPUSH 、LINSERT 等添加命令而被取消

通过将新元素推入造成客户端阻塞的某个键中,可以让相应的客户端从阻塞状态中脱离出来(取消阻塞的客户端数量取决于推入元素的数量)。
LPUSH 、RPUSH 和 LINSERT 这三个添加新元素到列表的命令,在底层都由一个

pushGenericCommand 的函数实现,这个函数的运作流程如下图:

当向一个空键推入新元素时,pushGenericCommand 函数执行以下两件事:

1. 检查这个键是否存在于前面提到的 server.db[i]->blocking_keys 字典里,如果是的 话,那么说明有至少一个客户端因为这个 key 而被阻塞,程序会为这个键创建一个 redis.h/readyList 结构,并将它添加到 server.ready_keys 链表中。

2. 将给定的值添加到列表键中。 readyList 结构的定义如下:

readyList 结构的 key 属性指向造成阻塞的键,而 db 则指向该键所在的数据库。

typedef struct readyList { 
    redisDb *db;

    robj *key;
} readyList;

举个例子,假设某个非阻塞客户端正在使用 0 号数据库,而这个数据库当前的 blocking_keys属性的值如下:

如果这时客户端对该数据库执行 PUSH key3 value ,那么 pushGenericCommand 将创建一个 db 属性指向 0 号数据库、key 属性指向 key3 键对象的 readyList 结构,并将它添加到服务器 server.ready_keys 属性的链表中:

在我们这个例子中,到目前为止,pushGenericCommand 函数完成了以下两件事:

1. 将readyList添加到服务器。
2. 将新元素value添加到键key3。

虽然 key3 已经不再是空键,但到目前为止,被 key3 阻塞的客户端还没有任何一个被解除阻塞状态。

为了做到这一点,Redis 的主进程在执行完 pushGenericCommand 函数之后,会继续调用 handleClientsBlockedOnLists 函数,这个函数执行以下操作:

  1. 如果 server.ready_keys 不为空,那么弹出该链表的表头元素,并取出元素中的 readyList 值。

  2. 根据readyList值所保存的key和db,在server.blocking_keys中查找所有因为key 而被阻塞的客户端(以链表的形式保存)。

  3. 如果key不为空,那么从key中弹出一个元素,并弹出客户端链表的第一个客户端,然 后将被弹出元素返回给被弹出客户端作为阻塞原语的返回值。

  4. 根据readyList结构的属性,删除server.blocking_keys中相应的客户端数据,取消 客户端的阻塞状态。

  5. 继续执行步骤 3 和 4 ,直到 key 没有元素可弹出,或者所有因为 key 而阻塞的客户端都

    取消阻塞为止。

  6. 继续执行步骤 1 ,直到 ready_keys 链表里的所有 readyList 结构都被处理完为止。用一段伪代码描述以上操作可能会更直观一些:

    def handleClientsBlockedOnLists(): 
        # 执行直到 ready_keys 为空
        while server.ready_keys != NULL: 
            # 弹出链表中的第一个 readyList
            rl = server.ready_keys.pop_first_node() 
            # 遍历所有因为这个键而被阻塞的客户端
            for client in all_client_blocking_by_key(rl.key, rl.db):
                # 只要还有客户端被这个键阻塞,就一直从键中弹出元素
                # 如果被阻塞客户端执行的是 BLPOP ,那么对键执行 LPOP 
                # 如果执行的是 BRPOP ,那么对键执行 RPOP
                element = rl.key.pop_element()
                if element == NULL:
                    # 键为空,跳出 for 循环
                    # 余下的未解除阻塞的客户端只能等待下次新元素的进入了 
                    break
                else:
                    # 清除客户端的阻塞信息                             
                    server.blocking_keys.remove_blocking_info(client)     
                    # 将元素返回给客户端,脱离阻塞状态 
                    client.reply_list_item(element)

    6、 先阻塞先服务(FBFS)策略

    值得一提的是,当程序添加一个新的被阻塞客户端到 server.blocking_keys 字典的链表中 时,它将该客户端放在链表的最后,而当 handleClientsBlockedOnLists 取消客户端的阻塞 时,它从链表的最前面开始取消阻塞:这个链表形成了一个 FIFO 队列,最先被阻塞的客户端 总值最先脱离阻塞状态,Redis 文档称这种模式为先阻塞先服务(FBFS,first-block-first-serve)。

    举个例子,在下图所示的阻塞状况中,如果客户端对数据库执行 PUSH key3 value ,那么只有 client3 会被取消阻塞,client6 和 client4 仍然阻塞;如果客户端对数据库执行 PUSH key3 value1 value2 ,那么 client3 和 client4 的阻塞都会被取消,而客户端 client6 依然处于 阻塞状态:

7、 阻塞因超过最大等待时间而被取消

前面提到过,当客户端被阻塞时,所有造成它阻塞的键,以及阻塞的最长时限会被记录在客户端里面,并且该客户端的状态会被设置为“正在阻塞” 。

每次 Redis 服务器常规操作函数(server cron job)执行时,程序都会检查所有连接到服务器 的客户端,查看那些处于“正在阻塞”状态的客户端的最大阻塞时限是否已经过期,如果是的话, 就给客户端返回一个空白回复,然后撤销对客户端的阻塞。

可以用一段伪代码来描述这个过程:

def server_cron_job(): 
    # 其他操作 ...
    # 遍历所有已连接客户端
    for client in server.all_connected_client:
    # 如果客户端状态为“正在阻塞”,并且最大阻塞时限已到达 
        if client.state == BLOCKING and \
        client.max_blocking_timestamp < current_timestamp(): 
        # 那么给客户端发送空回复, 脱离阻塞状态
        client.send_empty_reply()
        # 并清除客户端在服务器上的阻塞信息
        server.blocking_keys.remove_blocking_info(client) 
# 其他操作 ...

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

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

相关文章

进程通信知识基础【Linux】——下篇

目录 前文 一&#xff0c;命名管道 创建命名管道 1. getline——c库 2. unlink——系统接口 实践代码 common.hpp client.cpp server.cpp Log.cpp 二&#xff0c;共享内存&#xff08;system V接口&#xff09; 1. 创建共享内存 shmget接口 2. 删除共享内存 常见…

matlab中Signal Builder模块的用法总结

目录 前言方法一方法二参考文章 前言 今天在用matlab中Signal Builder的模块时&#xff0c;不知道怎么去得到想要的信号源&#xff0c;于是上网查了一下&#xff0c;并记录一下 方法一 如图所示&#xff0c;打开自定义 上面一行是横坐标&#xff0c;下面一行是纵坐标 [0,1…

SolidWorks二次开发 C#-读取基于Excel的BOM表信息

SolidWorks二次开发 C#-读取基于Excel的BOM表信息 问题点来源解决方案及思路相关引用链接 问题点来源 这是一位粉丝问的一个问题&#xff0c;他说到: 老师&#xff0c;请问Solidworks二次开发工程图中"基于Excel的材料明细表"怎么读取里面的数据&#xff1f; Ps:这…

ActionCLIP:A New Paradigm for Video Action Recognition

文章目录 ActionCLIP: A New Paradigm for Video Action Recognition动机创新点相关工作方法多模态框架新范式预训练提示微调 实验实验细节消融实验关键代码 总结相关参考 ActionCLIP: A New Paradigm for Video Action Recognition 论文&#xff1a;https://arxiv.org/abs/21…

vue使用el-tag完成添加标签操作

需求&#xff1a;做一个添加标签的功能&#xff0c;点击添加后输入内容后回车可以添加&#xff0c;并且标签可以删除 1.效果 2.主要代码讲解 鼠标按下后触发handleLabel函数&#xff0c;根据回车的keycode判断用户是不是按下的回车键&#xff0c;回车键键值为13&#xff0c;用…

【一种用opencv实现高斯曲线拟合的方法】

背景&#xff1a; 项目中需要实现数据的高斯拟合&#xff0c;进而提取数据中标准差&#xff0c;手头只有opencv库&#xff0c;经过资料查找验证&#xff0c;总结该方法。 基础知识&#xff1a; 1、opencv中solve可以实现对矩阵参数的求解&#xff1b; 2、线的拟合就是对多项…

Nginx配置请求头携带原始请求信息

在浏览器向nginx发送请求时&#xff0c;nginx会将请求转发给SpringBoot&#xff0c;此时由于是nginx给SpringBoot发送的请求&#xff0c;所以SpringBoot获取到的请求IP是192.168.1.2&#xff0c;而并非是浏览器的192.168.1.1&#xff0c;如果想要获取原始的请求IP&#xff0c;应…

【小程序】-【

swiper、swiper-item轮播图 swiper是滑块视图容器。其中只可放置swiper-item组件。部分常用属性如下&#xff0c;其余属性详见&#xff1a;官方文档 <view class"banner"><swiperprevious-margin"30rpx"circularautoplayinterval"3000&q…

Python glob

参考文章&#xff1a; Python 中glob.glob()、glob.iglob&#xff08;&#xff09;的使用-CSDN博客 Python 中glob.glob()的使用 glob.glob(path)的功能&#xff1a; 返回符合path格式的所有文件的路径&#xff0c;以list存储返回。 path的表示方法&#xff1a; 利用匹配符…

服务器挖矿木马识别与清理

一、什么是挖矿木马 挖矿木马会占用CPU进行超频运算,从而占用主机大量的CPU资源,严重影响服务器上的其他应用的正常运行。黑客为了得到更多的算力资源,一般都会对全网进行无差别扫描,同时利用SSH爆破和漏洞利用等手段攻击主机。部分挖矿木马还具备蠕虫化的特点,在主机被成…

【C语言加油站】qsort函数的模拟实现

qsort函数的模拟实现 导言一、回调函数二、冒泡排序2.1 冒泡排序实现升序 三、qsort函数3.1 qsort函数的使用3.2 比较函数 四、通过冒泡排序模拟实现qsort函数4.1 任务需求4.2 函数参数4.3 函数定义与声明4.4 函数实现4.4.1 函数主体4.4.2 比较函数4.4.3 元素交换 4.5 my_qsort…

Linux下I2C调试工具--for--Zynq MPSOC/Jetson Xavier

Linux下I2C调试工具 1、简介 i2c-tools是一个专门调试i2c的工具&#xff0c;无需编写任何代码即可轻松调试IC设备&#xff0c;可获取挂载的设备及设备地址&#xff0c;还可以在对应的设备指定寄存器设置值或者获取值等功能。i2c-tools有如下几个常用测试命令i2cdetect, i2cdu…

Stable LM Zephyr 3B:手机上的强大LLM助手

概览 最近&#xff0c;Stability.ai宣布开源了Stable LM Zephyr 3B&#xff0c;这是一个30亿参数的大语言模型&#xff08;LLM&#xff09;&#xff0c;专为手机、笔记本等移动设备设计。其突出的特点是参数较小、性能强大且算力消耗低&#xff0c;能够自动生成文本、总结摘要…

arcgis更改服务注册数据库账号及密码

最近服务器数据库密码换了&#xff0c;gis服务也得换下数据库连接密码。传统官方的更改方式&#xff08;上传连接配置文件&#xff09;&#xff1a; ArcGIS Server数据库注册篇(I) — 更新数据库密码_arcgis server sde换密码-CSDN博客 方式太麻烦了&#xff0c;需要安装ArcG…

Tektronix泰克TCP303示波器电流探头

主要特点和优点&#xff1a; ● 交流/直流测量功能 ● DC~100MHz电流探头放大器&#xff08;TCPA300&#xff09;&#xff0c;当使用&#xff1a; - DC~100MHz, 30A DC&#xff08;TCP312&#xff09; - DC~50MHz, 50A DC&#xff08;TCP305&#xff09; - DC~5MHz, 150A DC&a…

使用案例总结Vlookup函数的30种用法

1 基础用法 =VLOOKUP(A12,B$1:D$8,3,0) 2 批量查找 =VLOOKUP(A11:A13,A2:C8,3,0) 3 模糊查找 =VLOOKUP("*"&D2&"*",A:B,2,0) 4 模糊查找2 =VLOOKUP(D10&"??",A:B,2,0) 5 模糊查找3 =

GAMES101-Lec10~12几何 曲线 曲面网格

目录 1.几何的表示1.1显示1.1.1更多显示表示方法1.1.1.1点云1.1.1.2多边形网格 1.2隐示1.2.1更多隐示表达法1.2.1.1代数曲面1.2.1.2 CSG1.2.1.3距离函数SDF1.2.1.4水平集1.2.1.5分型几何 2.曲线2.1贝塞尔曲线2.2 计算方法2.3代数表示2.4性质2.5逐段贝塞尔曲线 3.曲面3.1贝塞尔曲…

如何使用Java在Excel中添加动态数组公式?

本文由葡萄城技术团队发布。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 前言 动态数组公式是 Excel 引入的一项重要功能&#xff0c;它将 Excel 分为两种风格&#xff1a;Excel 365 和传统 …

C语言使用posix正则表达式库

在C语言中&#xff0c;你可以使用 POSIX 正则表达式库&#xff08;regex.h&#xff09;来进行正则表达式的模式匹配。POSIX 正则表达式库提供了一组函数来编译、执行和释放正则表达式。 下面是使用 POSIX 正则表达式库的基本步骤&#xff1a; 包含头文件 <regex.h>&…

C语言——完数难题(头歌编程刷题)

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 生命如同寓言&#xff0c;其价值不在于…